chromium/ash/accelerators/ash_accelerator_configuration.cc

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

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

#include "ash/accelerators/ash_accelerator_configuration.h"

#include <vector>

#include "ash/accelerators/accelerator_prefs.h"
#include "ash/accelerators/accelerator_table.h"
#include "ash/accelerators/debug_commands.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/accelerators_util.h"
#include "ash/public/mojom/accelerator_configuration.mojom-shared.h"
#include "ash/public/mojom/accelerator_configuration.mojom.h"
#include "ash/public/mojom/accelerator_info.mojom.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"

namespace {

using AcceleratorActionMap = ui::AcceleratorMap<ash::AcceleratorAction>;
using ::ash::mojom::AcceleratorConfigResult;

constexpr char kAcceleratorModifiersKey[] = "modifiers";
constexpr char kAcceleratorKeyCodeKey[] = "key";
constexpr char kAcceleratorTypeKey[] = "type";
constexpr char kAcceleratorStateKey[] = "state";
constexpr char kAcceleratorKeyStateKey[] = "key_state";
constexpr char kAcceleratorModificationActionKey[] = "action";

PrefService* GetActiveUserPrefService() {
  if (!ash::Shell::HasInstance()) {
    return nullptr;
  }
  CHECK(ash::Shell::Get()->session_controller());

  return ash::Shell::Get()->session_controller()->GetActivePrefService();
}

void AppendAcceleratorData(
    std::vector<ash::AcceleratorData>& data,
    base::span<const ash::AcceleratorData> accelerators) {
  data.reserve(data.size() + accelerators.size());
  for (const auto& accelerator : accelerators) {
    data.push_back(accelerator);
  }
}

base::Value AcceleratorModificationDataToValue(
    const ui::Accelerator& accelerator,
    AcceleratorModificationAction action) {
  base::Value::Dict accelerator_values;
  accelerator_values.Set(kAcceleratorModifiersKey, accelerator.modifiers());
  accelerator_values.Set(kAcceleratorKeyCodeKey,
                         static_cast<int>(accelerator.key_code()));
  accelerator_values.Set(
      kAcceleratorTypeKey,
      static_cast<int>(ash::mojom::AcceleratorType::kDefault));
  accelerator_values.Set(
      kAcceleratorStateKey,
      static_cast<int>(ash::mojom::AcceleratorState::kEnabled));
  accelerator_values.Set(kAcceleratorKeyStateKey,
                         static_cast<int>(accelerator.key_state()));
  accelerator_values.Set(kAcceleratorModificationActionKey,
                         static_cast<int>(action));
  return base::Value(std::move(accelerator_values));
}

AcceleratorModificationData ValueToAcceleratorModificationData(
    const base::Value::Dict& value) {
  std::optional<int> keycode = value.FindInt(kAcceleratorKeyCodeKey);
  std::optional<int> modifier = value.FindInt(kAcceleratorModifiersKey);
  std::optional<int> modification_action =
      value.FindInt(kAcceleratorModificationActionKey);
  std::optional<int> key_state = value.FindInt(kAcceleratorKeyStateKey);
  CHECK(keycode.has_value());
  CHECK(modifier.has_value());
  CHECK(modification_action.has_value());
  ui::Accelerator accelerator(static_cast<ui::KeyboardCode>(*keycode),
                              static_cast<int>(*modifier));
  if (key_state.has_value()) {
    accelerator.set_key_state(
        static_cast<ui::Accelerator::KeyState>(key_state.value()));
  }
  return {accelerator,
          static_cast<AcceleratorModificationAction>(*modification_action)};
}

void SetLookupMaps(base::span<const ash::AcceleratorData> accelerators,
                   ash::ActionIdToAcceleratorsMap& id_to_accelerator,
                   AcceleratorActionMap& accelerator_to_id,
                   base::flat_set<ui::Accelerator>& locked_accelerator_set) {
  for (const auto& acceleratorData : accelerators) {
    ui::Accelerator accelerator(acceleratorData.keycode,
                                acceleratorData.modifiers);
    accelerator.set_key_state(acceleratorData.trigger_on_press
                                  ? ui::Accelerator::KeyState::PRESSED
                                  : ui::Accelerator::KeyState::RELEASED);
    accelerator_to_id.InsertNew(
        std::make_pair(accelerator, acceleratorData.action));
    id_to_accelerator[static_cast<uint32_t>(acceleratorData.action)].push_back(
        accelerator);
    if (acceleratorData.accelerator_locked) {
      locked_accelerator_set.insert(accelerator);
    }
  }
}

std::vector<ash::AcceleratorData> GetDefaultAccelerators() {
  std::vector<ash::AcceleratorData> accelerators;
  AppendAcceleratorData(
      accelerators,
      base::make_span(ash::kAcceleratorData, ash::kAcceleratorDataLength));

  if (::features::IsImprovedKeyboardShortcutsEnabled()) {
    AppendAcceleratorData(
        accelerators,
        base::make_span(ash::kEnableWithPositionalAcceleratorsData,
                        ash::kEnableWithPositionalAcceleratorsDataLength));
    AppendAcceleratorData(
        accelerators,
        base::make_span(
            ash::kEnabledWithImprovedDesksKeyboardShortcutsAcceleratorData,
            ash::
                kEnabledWithImprovedDesksKeyboardShortcutsAcceleratorDataLength));
  } else {
    AppendAcceleratorData(
        accelerators,
        base::make_span(ash::kDisableWithNewMappingAcceleratorData,
                        ash::kDisableWithNewMappingAcceleratorDataLength));
  }
  if (ash::features::IsSameAppWindowCycleEnabled()) {
    AppendAcceleratorData(
        accelerators,
        base::make_span(
            ash::kEnableWithSameAppWindowCycleAcceleratorData,
            ash::kEnableWithSameAppWindowCycleAcceleratorDataLength));
  }

  if (ash::features::IsGameDashboardEnabled()) {
    AppendAcceleratorData(
        accelerators,
        base::make_span(ash::kToggleGameDashboardAcceleratorData,
                        ash::kToggleGameDashboardAcceleratorDataLength));
  }

  if (ash::features::IsPickerUpdateEnabled()) {
    AppendAcceleratorData(
        accelerators, base::make_span(ash::kTogglePickerAcceleratorData,
                                      ash::kTogglePickerAcceleratorDataLength));
  }

  if (ash::features::IsTilingWindowResizeEnabled()) {
    AppendAcceleratorData(
        accelerators,
        base::make_span(ash::kTilingWindowResizeAcceleratorData,
                        ash::kTilingWindowResizeAcceleratorDataLength));
  }

  // Debug accelerators.
  if (ash::debug::DebugAcceleratorsEnabled()) {
    AppendAcceleratorData(accelerators,
                          base::make_span(ash::kDebugAcceleratorData,
                                          ash::kDebugAcceleratorDataLength));
  }

  // Developer accelerators.
  if (ash::debug::DeveloperAcceleratorsEnabled()) {
    AppendAcceleratorData(
        accelerators, base::make_span(ash::kDeveloperAcceleratorData,
                                      ash::kDeveloperAcceleratorDataLength));
  }
  return accelerators;
}

}  // namespace

namespace ash {

AshAcceleratorConfiguration::AshAcceleratorConfiguration()
    : AcceleratorConfiguration(ash::mojom::AcceleratorSource::kAsh) {
  if (Shell::HasInstance()) {
    Shell::Get()->session_controller()->AddObserver(this);
  }
}
AshAcceleratorConfiguration::~AshAcceleratorConfiguration() {
  if (Shell::HasInstance()) {
    Shell::Get()->session_controller()->RemoveObserver(this);
  }
}

// TODO(jimmyxgong): Implement all functions below as these are only stubs.

// static:
void AshAcceleratorConfiguration::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterDictionaryPref(prefs::kShortcutCustomizationOverrides);
}

void AshAcceleratorConfiguration::OnActiveUserPrefServiceChanged(
    PrefService* pref_service) {
  // A pref service may not be available in tests.
  if (!pref_service || pref_service != GetActiveUserPrefService() ||
      !Shell::Get()->accelerator_prefs()->IsCustomizationAllowed()) {
    return;
  }

  // Store a copy of the pref overrides.
  accelerator_overrides_ =
      pref_service->GetDict(prefs::kShortcutCustomizationOverrides).Clone();

  base::UmaHistogramCounts1000(
      "Ash.ShortcutCustomization.CustomizationsLoadedOnStartup",
      GetTotalNumberOfModifications());

  if (features::IsResetShortcutCustomizationsEnabled()) {
    VLOG(1) << "ResetShortcutCustomizations flag enabled, "
            << "resetting all shortcuts.";
    RestoreAllDefaults();
  } else {
    ResetAllAccelerators();
    ApplyPrefOverrides();
  }
}

base::optional_ref<const std::vector<ui::Accelerator>>
AshAcceleratorConfiguration::GetAcceleratorsForAction(
    AcceleratorActionId action_id) {
  const auto accelerator_iter = id_to_accelerators_.find(action_id);
  if (accelerator_iter == id_to_accelerators_.end()) {
    return std::nullopt;
  }

  return accelerator_iter->second;
}

bool AshAcceleratorConfiguration::IsMutable() const {
  return Shell::Get()->accelerator_prefs()->IsCustomizationAllowed();
}

bool AshAcceleratorConfiguration::IsDeprecated(
    const ui::Accelerator& accelerator) const {
  return deprecated_accelerators_to_id_.Find(accelerator);
}

bool AshAcceleratorConfiguration::IsAcceleratorLocked(
    const ui::Accelerator& accelerator) const {
  return locked_accelerator_set_.contains(accelerator);
}

const AcceleratorAction* AshAcceleratorConfiguration::FindAcceleratorAction(
    const ui::Accelerator& accelerator) const {
  // If the accelerator is deprecated, return the action ID first.
  const AcceleratorAction* deprecated_action_id =
      deprecated_accelerators_to_id_.Find(accelerator);
  if (deprecated_action_id) {
    return deprecated_action_id;
  }

  return accelerator_to_id_.Find(accelerator);
}

AcceleratorConfigResult AshAcceleratorConfiguration::AddUserAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& accelerator) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());
  const AcceleratorConfigResult result =
      DoAddAccelerator(action_id, accelerator, /*save_override=*/true);

  if (result == AcceleratorConfigResult::kSuccess) {
    UpdateAndNotifyAccelerators();
  }

  VLOG(1) << "AddAccelerator called for ActionID: " << action_id
          << ", Accelerator: " << accelerator.GetShortcutText()
          << " returned: " << static_cast<int>(result);

  return result;
}

AcceleratorConfigResult AshAcceleratorConfiguration::RemoveAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& accelerator) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());
  AcceleratorConfigResult result =
      DoRemoveAccelerator(action_id, accelerator, /*save_override=*/true);

  if (result == AcceleratorConfigResult::kSuccess) {
    UpdateAndNotifyAccelerators();
  }

  VLOG(1) << "RemovedAccelerator called for ActionID: " << action_id
          << ", Accelerator: " << accelerator.GetShortcutText()
          << " returned: " << static_cast<int>(result);
  return result;
}

AcceleratorConfigResult AshAcceleratorConfiguration::ReplaceAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& old_accelerator,
    const ui::Accelerator& new_accelerator) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());

  const AcceleratorConfigResult result =
      DoReplaceAccelerator(action_id, old_accelerator, new_accelerator);
  if (result == AcceleratorConfigResult::kSuccess) {
    UpdateAndNotifyAccelerators();
  }

  VLOG(1) << "ReplaceAccelerator caleld for ActionID: " << action_id
          << ", old accelerator: " << old_accelerator.GetShortcutText()
          << ", new accelerator: " << new_accelerator.GetShortcutText()
          << " returned: " << static_cast<int>(result);
  return result;
}

AcceleratorConfigResult AshAcceleratorConfiguration::RestoreDefault(
    AcceleratorActionId action_id) {
  const auto& current_accelerators = id_to_accelerators_.find(action_id);
  if (current_accelerators == id_to_accelerators_.end()) {
    VLOG(1) << "ResetAction called for ActionID: " << action_id
            << " returned with error: " << AcceleratorConfigResult::kNotFound;
    return AcceleratorConfigResult::kNotFound;
  }

  auto& accelerators_for_id = current_accelerators->second;
  // Clear reverse mapping first.
  for (const auto& accelerator : accelerators_for_id) {
    // There should never be a mismatch between the two maps, `Get()` does an
    // implicit CHECK too.
    auto& found_id = accelerator_to_id_.Get(accelerator);
    if (found_id != action_id) {
      VLOG(1) << "ResetAction called for ActionID: " << action_id
              << " returned with error: " << AcceleratorConfigResult::kNotFound;
      return AcceleratorConfigResult::kNotFound;
    }

    accelerator_to_id_.Erase(accelerator);
  }

  // Clear lookup map.
  accelerators_for_id.clear();

  // Restore the system default accelerator(s) for this action only if it the
  // default is not used by another accelerator.
  // Users will have to manually re-add the default accelerator if there exists
  // a conflict.
  const auto& defaults = default_id_to_accelerators_cache_.find(action_id);
  CHECK(defaults != default_id_to_accelerators_cache_.end());

  AcceleratorConfigResult result = AcceleratorConfigResult::kSuccess;
  // Iterate through the default and only add back the default if they're not
  // in use.
  for (const auto& default_accelerator : defaults->second) {
    if (!accelerator_to_id_.Find(default_accelerator)) {
      accelerators_for_id.push_back(default_accelerator);
      accelerator_to_id_.InsertNew(
          {default_accelerator, static_cast<AcceleratorAction>(action_id)});
    } else {
      // The default accelerator cannot be re-added since it conflicts with
      // another accelerator.
      result = AcceleratorConfigResult::kRestoreSuccessWithConflicts;
    }
  }

  // Update the pref overrides.
  std::string id = base::NumberToString(action_id);
  const auto* found_override = accelerator_overrides_.Find(id);
  if (found_override) {
    accelerator_overrides_.Remove(id);
  }

  UpdateAndNotifyAccelerators();

  VLOG(1) << "ResetAction called for ActionID: " << action_id << " returned "
          << static_cast<uint32_t>(result);
  return result;
}

AcceleratorConfigResult AshAcceleratorConfiguration::RestoreAllDefaults() {
  base::UmaHistogramCounts1000(
      "Ash.ShortcutCustomization.CustomizationsBeforeResetAll",
      GetTotalNumberOfModifications());

  ResetAllAccelerators();

  // Clear the prefs to be back to default.
  accelerator_overrides_.clear();

  UpdateAndNotifyAccelerators();

  return AcceleratorConfigResult::kSuccess;
}

void AshAcceleratorConfiguration::Initialize() {
  Initialize(GetDefaultAccelerators());
  InitializeDeprecatedAccelerators();
}

void AshAcceleratorConfiguration::Initialize(
    base::span<const AcceleratorData> accelerators) {
  accelerators_.clear();
  deprecated_accelerators_to_id_.Clear();
  id_to_accelerators_.clear();
  accelerator_to_id_.Clear();
  default_accelerators_to_id_cache_.Clear();
  default_id_to_accelerators_cache_.clear();

  // Cache these accelerators as the default.
  SetLookupMaps(accelerators, default_id_to_accelerators_cache_,
                default_accelerators_to_id_cache_, locked_accelerator_set_);

  // TODO(jimmyxgong): Before adding the accelerators to the mappings, apply
  // pref remaps.
  AddAccelerators(accelerators);
}

void AshAcceleratorConfiguration::InitializeDeprecatedAccelerators() {
  base::span<const DeprecatedAcceleratorData> deprecated_accelerator_data(
      kDeprecatedAcceleratorsData, kDeprecatedAcceleratorsDataLength);
  base::span<const AcceleratorData> deprecated_accelerators(
      kDeprecatedAccelerators, kDeprecatedAcceleratorsLength);

  InitializeDeprecatedAccelerators(std::move(deprecated_accelerator_data),
                                   std::move(deprecated_accelerators));
}

void AshAcceleratorConfiguration::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void AshAcceleratorConfiguration::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

bool AshAcceleratorConfiguration::HasObserver(Observer* observer) {
  return observer_list_.HasObserver(observer);
}

// This function must only be called after Initialize().
void AshAcceleratorConfiguration::InitializeDeprecatedAccelerators(
    base::span<const DeprecatedAcceleratorData> deprecated_data,
    base::span<const AcceleratorData> deprecated_accelerators) {
  for (const auto& data : deprecated_data) {
    actions_with_deprecations_[data.action] = &data;
  }

  for (const auto& data : deprecated_accelerators) {
    deprecated_accelerators_to_id_.InsertNew(
        {{data.keycode, data.modifiers},
         static_cast<AcceleratorAction>(data.action)});
  }

  // Cache a copy of the default deprecated accelerators.
  default_actions_with_deprecations_cache_ = actions_with_deprecations_;
  default_deprecated_accelerators_to_id_cache_ = deprecated_accelerators_to_id_;
  UpdateAndNotifyAccelerators();
}

void AshAcceleratorConfiguration::AddAccelerators(
    base::span<const AcceleratorData> accelerators) {
  SetLookupMaps(accelerators, id_to_accelerators_, accelerator_to_id_,
                locked_accelerator_set_);
  UpdateAndNotifyAccelerators();
}

AcceleratorConfigResult AshAcceleratorConfiguration::DoRemoveAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& accelerator,
    bool save_override) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());

  // If the accelerator is deprecated, remove it.
  const AcceleratorAction* deprecated_action_id =
      deprecated_accelerators_to_id_.Find(accelerator);
  if (deprecated_action_id && *deprecated_action_id == action_id) {
    deprecated_accelerators_to_id_.Erase(accelerator);
    // Check if there are any other accelerators associated with `action_id`.
    // If not, remove it from `actions_with_deprecations_`.
    bool has_more_deprecated_accelerators = false;
    for (const auto& deprecated_iter : deprecated_accelerators_to_id_) {
      if (deprecated_iter.second == action_id) {
        has_more_deprecated_accelerators = true;
        break;
      }
    }

    if (!has_more_deprecated_accelerators) {
      actions_with_deprecations_.erase(action_id);
    }
    return AcceleratorConfigResult::kSuccess;
  }

  const AcceleratorAction* found_id = accelerator_to_id_.Find(accelerator);
  auto found_accelerators_iter = id_to_accelerators_.find(action_id);
  if (found_accelerators_iter == id_to_accelerators_.end() || !found_id) {
    return AcceleratorConfigResult::kNotFound;
  }

  CHECK(*found_id == action_id);

  // Remove accelerator from lookup map.
  std::erase(found_accelerators_iter->second, accelerator);

  // Remove accelerator from reverse lookup map.
  accelerator_to_id_.Erase(accelerator);

  // Also remove accelerators in the reverse key_state.
  ui::Accelerator accelerator_reverse_state(accelerator);
  accelerator_reverse_state.set_key_state(
      accelerator.key_state() == ui::Accelerator::KeyState::PRESSED
          ? ui::Accelerator::KeyState::RELEASED
          : ui::Accelerator::KeyState::PRESSED);

  const AcceleratorAction* reverse_key_state_id =
      accelerator_to_id_.Find(accelerator_reverse_state);

  if (reverse_key_state_id && *reverse_key_state_id == action_id) {
    std::erase(found_accelerators_iter->second, accelerator_reverse_state);
    accelerator_to_id_.Erase(accelerator_reverse_state);
  }

  // Store the final state of `action_id`.
  if (save_override) {
    UpdateOverrides(action_id, accelerator,
                    AcceleratorModificationAction::kRemove);
  }

  return AcceleratorConfigResult::kSuccess;
}

AcceleratorConfigResult AshAcceleratorConfiguration::DoAddAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& accelerator,
    bool save_override) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());

  const auto& accelerators_iter = id_to_accelerators_.find(action_id);
  if (accelerators_iter == id_to_accelerators_.end()) {
    return AcceleratorConfigResult::kNotFound;
  }

  // Check if `accelerator` is already used, in-use or deprecated. If so
  // remove/disable it.
  const auto* conflict_action_id = FindAcceleratorAction(accelerator);
  if (conflict_action_id) {
    // If the conflicting accelerator is NOT the default for culprit action id,
    // then we should update the override accordingly. Otherwise, we do not
    // save the override as it will be handled implicitly when applying the
    // prefs.
    bool save_remove_override = true;
    std::optional<AcceleratorAction> conflict_accelerator_default_id =
        GetIdForDefaultAccelerator(accelerator);
    if (conflict_accelerator_default_id.has_value()) {
      save_remove_override =
          conflict_accelerator_default_id.value() != *conflict_action_id;
    }
    const AcceleratorConfigResult remove_result = DoRemoveAccelerator(
        *conflict_action_id, accelerator, save_remove_override);
    if (remove_result != AcceleratorConfigResult::kSuccess) {
      return remove_result;
    }
  }

  // Add the accelerator.
  auto& accelerators = accelerators_iter->second;
  accelerators.push_back(accelerator);
  accelerator_to_id_.InsertNew(
      {accelerator, static_cast<AcceleratorAction>(action_id)});

  if (save_override) {
    // Update pref overrides.
    UpdateOverrides(action_id, accelerator,
                    AcceleratorModificationAction::kAdd);
  }

  return AcceleratorConfigResult::kSuccess;
}

mojom::AcceleratorConfigResult
AshAcceleratorConfiguration::DoReplaceAccelerator(
    AcceleratorActionId action_id,
    const ui::Accelerator& old_accelerator,
    const ui::Accelerator& new_accelerator) {
  CHECK(Shell::Get()->accelerator_prefs()->IsCustomizationAllowed());

  // Check that `old_accelerator` belongs to `action_id`.
  const AcceleratorAction* found_id = accelerator_to_id_.Find(old_accelerator);
  if (!found_id || *found_id != action_id) {
    return AcceleratorConfigResult::kNotFound;
  }

  // First remove the old accelerator.
  const AcceleratorConfigResult remove_result =
      DoRemoveAccelerator(action_id, old_accelerator, /*save_override=*/true);
  if (remove_result != AcceleratorConfigResult::kSuccess) {
    return remove_result;
  }

  // Now add the new accelerator.
  return DoAddAccelerator(action_id, new_accelerator, /*save_override=*/true);
}

void AshAcceleratorConfiguration::SetUsePositionalLookup(
    bool use_positional_lookup) {
  accelerator_to_id_.set_use_positional_lookup(use_positional_lookup);
  deprecated_accelerators_to_id_.set_use_positional_lookup(
      use_positional_lookup);
  default_accelerators_to_id_cache_.set_use_positional_lookup(
      use_positional_lookup);
  default_deprecated_accelerators_to_id_cache_.set_use_positional_lookup(
      use_positional_lookup);
}

const DeprecatedAcceleratorData*
AshAcceleratorConfiguration::GetDeprecatedAcceleratorData(
    AcceleratorActionId action) {
  auto it = actions_with_deprecations_.find(action);
  if (it == actions_with_deprecations_.end()) {
    return nullptr;
  }
  return it->second;
}

void AshAcceleratorConfiguration::NotifyAcceleratorsUpdated() {
  if (!Shell::Get()->accelerator_prefs()->IsCustomizationAllowed()) {
    return;
  }

  for (auto& observer : observer_list_) {
    observer.OnAcceleratorsUpdated();
  }
}

std::optional<AcceleratorAction>
AshAcceleratorConfiguration::GetIdForDefaultAccelerator(
    ui::Accelerator accelerator) {
  AcceleratorAction* found_id =
      default_accelerators_to_id_cache_.Find(accelerator);
  return found_id ? std::optional<AcceleratorAction>(*found_id) : std::nullopt;
}

std::vector<ui::Accelerator>
AshAcceleratorConfiguration::GetDefaultAcceleratorsForId(
    AcceleratorActionId id) {
  const auto iter = default_id_to_accelerators_cache_.find(id);

  if (iter == default_id_to_accelerators_cache_.end()) {
    VLOG(1) << "No default accelerators were found for id: " << id;
    return std::vector<ui::Accelerator>();
  }

  return iter->second;
}

bool AshAcceleratorConfiguration::IsValid(uint32_t id) const {
  return id_to_accelerators_.contains(id) &&
         default_id_to_accelerators_cache_.contains(id);
}

void AshAcceleratorConfiguration::UpdateAndNotifyAccelerators() {
  // Re-populate `accelerators_` which contains all currently available
  // accelerators and deprecated accelerators, if present.
  accelerators_.clear();
  accelerators_.reserve(accelerator_to_id_.size() +
                        deprecated_accelerators_to_id_.size());
  for (const auto& [accel, action_id] : accelerator_to_id_) {
    accelerators_.push_back(accel);
  }

  for (const auto& [accel, action_id] : deprecated_accelerators_to_id_) {
    accelerators_.push_back(accel);
  }

  UpdateAccelerators(id_to_accelerators_);
  NotifyAcceleratorsUpdated();
  if (Shell::Get()->accelerator_prefs()->IsCustomizationAllowed()) {
    SaveOverridePrefChanges();
  }
}

void AshAcceleratorConfiguration::SaveOverridePrefChanges() {
  auto* pref_service = GetActiveUserPrefService();
  if (pref_service) {
    pref_service->SetDict(prefs::kShortcutCustomizationOverrides,
                          accelerator_overrides_.Clone());
  }
}

void AshAcceleratorConfiguration::ApplyPrefOverrides() {
  // Stores all actions with prefs to be removed, this gets populated if there
  // are malformed prefs which results in an empty pref after removal.
  std::vector<uint32_t> actions_to_be_removed;

  for (auto entry : accelerator_overrides_) {
    int action_id;
    if (!base::StringToInt(entry.first, &action_id)) {
      LOG(ERROR) << "Failed to convert entry from accelerator overrides "
                 << "to an action ID. Skipping applying customization "
                 << "for this action.";
      continue;
    }
    if (!IsValid(action_id)) {
      VLOG(1) << "Invalid action ID: " << action_id
              << " found, queuing the ID to be removed.";
      actions_to_be_removed.push_back(action_id);
      continue;
    }

    if (!entry.second.is_list()) {
      LOG(ERROR) << "Entry for action ID: " << action_id << " does not contain "
                 << "a list. Skipping applying customization for this action.";
      continue;
    }
    base::Value::List& override_list = entry.second.GetList();
    if (override_list.empty()) {
      VLOG(1) << "Override list is unexpectedly empty for action ID: "
              << action_id << ". Skipping applying customization for "
              << "this action.";
      continue;
    }

    auto override_list_iter = override_list.begin();
    while (override_list_iter != override_list.end()) {
      if (!override_list_iter->is_dict()) {
        LOG(ERROR) << "Override list does not contain a dict, skipping "
                   << "applying customization for action ID: " << action_id;
        continue;
      }
      base::Value::Dict& override_dict = override_list_iter->GetDict();
      AcceleratorModificationData override_data =
          ValueToAcceleratorModificationData(override_dict);
      if (override_data.action == AcceleratorModificationAction::kRemove) {
        // Race condition:
        // If the user has disabled the default accelerator but then adds
        // it to another action, we do not attempt to remove here.
        const auto* found_id =
            accelerator_to_id_.Find(override_data.accelerator);

        // If the pref has an accelerator that is invalid, do not attempt to
        // apply the pref and remove it.
        if (!found_id) {
          override_list_iter = override_list.erase(override_list_iter);
          // If removing the pref results in an empty pref, remove it and move
          // onto the next override pref.
          if (override_list.empty()) {
            actions_to_be_removed.push_back(action_id);
            break;
          }
          continue;
        }
        if (*found_id == action_id) {
          DoRemoveAccelerator(action_id, override_data.accelerator,
                              /*save_override=*/false);
        }
      }

      if (override_data.action == AcceleratorModificationAction::kAdd) {
        DoAddAccelerator(action_id, override_data.accelerator,
                         /*save_override=*/false);
      }
      ++override_list_iter;
    }
  }

  // Remove all empty override prefs.
  for (uint32_t action : actions_to_be_removed) {
    accelerator_overrides_.Remove(base::NumberToString(action));
  }

  // Check if the overridden accelerators are valid, if not then restore all
  // defaults.
  if (!AreAcceleratorsValid()) {
    LOG(ERROR) << "Detected an error while applying shortcut customization "
               << "prefs. Restoring to default.";
    RestoreAllDefaults();
  }

  UpdateAndNotifyAccelerators();
}

void AshAcceleratorConfiguration::UpdateOverrides(
    AcceleratorActionId action_id,
    const ui::Accelerator& accelerator,
    AcceleratorModificationAction action) {
  const std::string action_id_key = base::NumberToString(action_id);
  base::Value* action_entry = accelerator_overrides_.Find(action_id_key);

  if (!action_entry) {
    base::Value::List accelerator_override_list;
    // No existing overrides, add the override entry and return.
    accelerator_override_list.Append(
        AcceleratorModificationDataToValue(accelerator, action));
    accelerator_overrides_.Set(action_id_key,
                               std::move(accelerator_override_list));
    return;
  }

  if (!action_entry->is_list()) {
    LOG(ERROR) << "Attempting to update overrides failed, action_entry is not "
               << "a list. Cannot apply updates for action ID: " << action_id;
    return;
  }
  base::Value::List& override_list = action_entry->GetList();
  if (action_entry->GetList().empty()) {
    VLOG(1) << "No entries inside action ID: " << action_id
            << ". Cannot apply updates.";
    return;
  }

  // Iterate through the override list, check if the accelerator already exist
  // for `action_id`.
  for (auto override_iter = override_list.begin();
       override_iter != override_list.end(); ++override_iter) {
    if (!override_iter->is_dict()) {
      LOG(ERROR) << "Override list does not contain a dict, cannot apply "
                 << "update for action ID: " << action_id;
      return;
    }
    const AcceleratorModificationData accelerator_data =
        ValueToAcceleratorModificationData(override_iter->GetDict());
    if (accelerator == accelerator_data.accelerator) {
      // It's not possible to perform the same action to the same accelerator.
      CHECK(accelerator_data.action != action);
      // The accelerator already has already been modified and the new
      // action differs from the previous modification action. This can happen
      // if the user has added a new custom accelerator and then removes it.
      // Or if the user disables a default accelerator and re-enables it.
      override_iter = override_list.erase(override_iter);
      if (override_list.empty()) {
        // If the override list is empty, no changes are made to `action_id`,
        // remove its override entry.
        accelerator_overrides_.Remove(action_id_key);
      }
      return;
    }
  }

  // The accelerator was not present in existing overrides, append this
  // accelerator.
  override_list.Append(AcceleratorModificationDataToValue(accelerator, action));
}

bool AshAcceleratorConfiguration::AreAcceleratorsValid() {
  // Iterate through the lookup map and verify that both lookup and reverse
  // lookup maps are in sync.
  // TODO(jimmyxgong): Consider resetting the override pref here if invalid
  // pref is found.
  for (const auto& [action_id, accelerators] : id_to_accelerators_) {
    // Perform reverse lookup of each of the accelerators and check that they
    // match `action_id`.
    for (const auto& accelerator : accelerators) {
      const AcceleratorAction* found_id = accelerator_to_id_.Find(accelerator);
      if (!found_id || *found_id != action_id) {
        LOG(ERROR) << "Shortcut override prefs are out of sync. Lookup map"
                   << " has an extra accelerator: "
                   << accelerator.GetShortcutText()
                   << "Reverting to default accelerators.";
        RestoreAllDefaults();
        return false;
      }
    }
  }

  // Now iterate through the reverse lookup. This is to check that all
  // accelerators in the reverse lookup are valid.
  for (const auto& [accelerator, action_id] : accelerator_to_id_) {
    const auto& id_to_accelerator_iter = id_to_accelerators_.find(action_id);
    if (id_to_accelerator_iter == id_to_accelerators_.end()) {
      LOG(ERROR) << "Shortcut overide prefs are out of sync, reverse lookup "
                 << " has an extra action id: " << action_id
                 << " Reverting to default accelerators.";
      RestoreAllDefaults();
      return false;
    }
    if (base::ranges::find(id_to_accelerator_iter->second, accelerator) ==
        id_to_accelerator_iter->second.end()) {
      LOG(ERROR) << "Shortcut overide prefs are out of sync, reverse lookup "
                 << "has an extra accelerator: "
                 << accelerator.GetShortcutText() << " for id: " << action_id
                 << " Reverting to default accelerators.";
      RestoreAllDefaults();
      return false;
    }
  }

  return true;
}

void AshAcceleratorConfiguration::ResetAllAccelerators() {
  accelerators_.clear();
  id_to_accelerators_.clear();
  accelerator_to_id_.Clear();
  deprecated_accelerators_to_id_.Clear();
  actions_with_deprecations_.clear();

  id_to_accelerators_ = default_id_to_accelerators_cache_;
  accelerator_to_id_ = default_accelerators_to_id_cache_;

  deprecated_accelerators_to_id_ = default_deprecated_accelerators_to_id_cache_;
  actions_with_deprecations_ = default_actions_with_deprecations_cache_;
}

int AshAcceleratorConfiguration::GetTotalNumberOfModifications() {
  int num_entries = 0;
  for (const auto entry : accelerator_overrides_) {
    num_entries += entry.second.GetList().size();
  }
  return num_entries;
}

}  // namespace ash