chromium/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_impl.cc

// Copyright 2023 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/input_device_settings/pref_handlers/keyboard_pref_handler_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/mojom/input_device_settings.mojom-shared.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_defaults.h"
#include "ash/system/input_device_settings/input_device_settings_metrics_manager.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/input_device_settings/input_device_settings_utils.h"
#include "ash/system/input_device_settings/input_device_tracker.h"
#include "ash/system/input_device_settings/settings_updated_metrics_info.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/json/values_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/mojom/extended_fkeys_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/extended_fkeys_modifier.mojom.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/mojom/modifier_key.mojom.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom.h"
#include "ui/events/ash/pref_names.h"

namespace ash {
namespace {

// Whether or not settings taken during the transition period should be
// persisted to the prefs. Values should only ever be true if the original
// setting was a user-configured value.

// Modifier remappings are not included here as it is only ever persisted if it
// is non-default.
struct ForceKeyboardSettingPersistence {
  bool top_row_are_fkeys = false;
  bool suppress_meta_fkey_rewrites = false;
};

static constexpr auto kKeyboardModifierMappings =
    base::MakeFixedFlatMap<ui::mojom::ModifierKey, const char*>(
        {{ui::mojom::ModifierKey::kAlt, ::prefs::kLanguageRemapAltKeyTo},
         {ui::mojom::ModifierKey::kControl,
          ::prefs::kLanguageRemapControlKeyTo},
         {ui::mojom::ModifierKey::kEscape, ::prefs::kLanguageRemapEscapeKeyTo},
         {ui::mojom::ModifierKey::kBackspace,
          ::prefs::kLanguageRemapBackspaceKeyTo},
         {ui::mojom::ModifierKey::kAssistant,
          ::prefs::kLanguageRemapAssistantKeyTo},
         {ui::mojom::ModifierKey::kCapsLock,
          ::prefs::kLanguageRemapCapsLockKeyTo}});

static constexpr auto kMetaKeyMapping =
    base::MakeFixedFlatMap<ui::mojom::MetaKey, const char*>(
        {{ui::mojom::MetaKey::kSearch, ::prefs::kLanguageRemapSearchKeyTo},
         {ui::mojom::MetaKey::kLauncher, ::prefs::kLanguageRemapSearchKeyTo},
         {ui::mojom::MetaKey::kExternalMeta,
          ::prefs::kLanguageRemapExternalMetaKeyTo},
         {ui::mojom::MetaKey::kCommand,
          ::prefs::kLanguageRemapExternalCommandKeyTo}});

bool GetDefaultTopRowAreFKeysValue(
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  if (keyboard_policies.top_row_are_fkeys_policy &&
      keyboard_policies.top_row_are_fkeys_policy->policy_status ==
          mojom::PolicyStatus::kRecommended) {
    return keyboard_policies.top_row_are_fkeys_policy->value;
  }

  return keyboard.is_external ? kDefaultTopRowAreFKeysExternal
                              : kDefaultTopRowAreFKeys;
}

bool IsAppleKeyboardDefaultModifierRemapping(ui::mojom::ModifierKey from,
                                             ui::mojom::ModifierKey to) {
  return (from == ui::mojom::ModifierKey::kMeta &&
          to == ui::mojom::ModifierKey::kControl) ||
         (from == ui::mojom::ModifierKey::kControl &&
          to == ui::mojom::ModifierKey::kMeta);
}

bool ShouldAddSixPackKeyProperties(const mojom::Keyboard& keyboard) {
  return features::IsAltClickAndSixPackCustomizationEnabled() &&
         !base::Contains(keyboard.modifier_keys,
                         ui::mojom::ModifierKey::kFunction);
}

bool ShouldAddExtendedFkeyProperties(const mojom::Keyboard& keyboard) {
  return ::features::AreF11AndF12ShortcutsEnabled() &&
         IsChromeOSKeyboard(keyboard) &&
         !base::Contains(keyboard.modifier_keys,
                         ui::mojom::ModifierKey::kFunction);
}

const char* GetDefaultKeyboardPref(const mojom::Keyboard& keyboard) {
  if (IsSplitModifierKeyboard(keyboard)) {
    return prefs::kKeyboardDefaultSplitModifierSettings;
  }

  return IsChromeOSKeyboard(keyboard)
             ? prefs::kKeyboardDefaultChromeOSSettings
             : prefs::kKeyboardDefaultNonChromeOSSettings;
}

ui::mojom::ExtendedFkeysModifier GetDefaultF11KeyValue(
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  if (keyboard_policies.f11_key_policy &&
      keyboard_policies.f11_key_policy->policy_status ==
          mojom::PolicyStatus::kRecommended) {
    return keyboard_policies.f11_key_policy->value;
  }

  return kDefaultFkey;
}

ui::mojom::ExtendedFkeysModifier GetDefaultF12KeyValue(
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  if (keyboard_policies.f12_key_policy &&
      keyboard_policies.f12_key_policy->policy_status ==
          mojom::PolicyStatus::kRecommended) {
    return keyboard_policies.f12_key_policy->value;
  }

  return kDefaultFkey;
}

bool GetDefaultSuppressMetaFkeyRewritesValue(
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  if (keyboard_policies.enable_meta_fkey_rewrites_policy &&
      keyboard_policies.enable_meta_fkey_rewrites_policy->policy_status ==
          mojom::PolicyStatus::kRecommended) {
    // Invert the value of the policy when getting the default value for the
    // setting, because the policy determines whether meta fkey rewrites are
    // enabled, and the setting controls whether meta fkey rewrites are
    // disabled.
    return !keyboard_policies.enable_meta_fkey_rewrites_policy->value;
  }

  return kDefaultSuppressMetaFKeyRewrites;
}

int GetSixPackKeyPrefCount(PrefService* prefs, const char* pref_name) {
  const auto* pref = prefs->GetUserPrefValue(pref_name);
  return pref ? pref->GetInt() : 0;
}

// Return the modifier used more frequently, in case of a tie, Search will
// be preferred to avoid Alt-based issues.
// Default setting:
//  Pref contains a positive value: SixPackShortcutModifier::kAlt
//  Pref contains a negative value: SixPackShortcutModifier::kSearch
ui::mojom::SixPackShortcutModifier GetSixPackKeyModifierFromPrefCount(
    int count) {
  return count <= 0 ? ui::mojom::SixPackShortcutModifier::kSearch
                    : ui::mojom::SixPackShortcutModifier::kAlt;
}

ui::mojom::SixPackShortcutModifier GetSixPackShortcutModifierFromSettingsDict(
    const base::Value::Dict& six_pack_key_remappings_dict,
    const char* pref_name) {
  return static_cast<ui::mojom::SixPackShortcutModifier>(
      *six_pack_key_remappings_dict.FindInt(pref_name));
}

// Each pref contains an integer value which may be positive or negative
// depending on whether the user uses the Alt or Search based rewrite more
// frequently. When dealing with the grouped 6-pack keys
// (PageUp/PageDown, Home/End), both prefs will be used when determining what
// value to set to avoid setting inconsistent values for similar 6-pack keys.
// If `prefs` is nullptr, return defaults.
mojom::SixPackKeyInfoPtr GetSixPackKeyRemappings(PrefService* prefs) {
  mojom::SixPackKeyInfoPtr six_pack_key_info = mojom::SixPackKeyInfo::New();
  if (!prefs) {
    return six_pack_key_info;
  }

  const auto page_up_down_modifier = GetSixPackKeyModifierFromPrefCount(
      GetSixPackKeyPrefCount(prefs, prefs::kKeyEventRemappedToSixPackPageDown) +
      GetSixPackKeyPrefCount(prefs, prefs::kKeyEventRemappedToSixPackPageUp));
  six_pack_key_info->page_down = page_up_down_modifier;
  six_pack_key_info->page_up = page_up_down_modifier;

  const auto home_end_modifier = GetSixPackKeyModifierFromPrefCount(
      GetSixPackKeyPrefCount(prefs, prefs::kKeyEventRemappedToSixPackHome) +
      GetSixPackKeyPrefCount(prefs, prefs::kKeyEventRemappedToSixPackEnd));
  six_pack_key_info->home = home_end_modifier;
  six_pack_key_info->end = home_end_modifier;

  six_pack_key_info->del = GetSixPackKeyModifierFromPrefCount(
      GetSixPackKeyPrefCount(prefs, prefs::kKeyEventRemappedToSixPackDelete));

  // The "Insert" key is always set to `kSearch` since the
  // (Search+Shift+Backspace) rewrite is the only way to emit an "Insert" key
  // event.
  six_pack_key_info->insert = ui::mojom::SixPackShortcutModifier::kSearch;
  return six_pack_key_info;
}

base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>
GetModifierRemappings(PrefService* prefs, const mojom::Keyboard& keyboard) {
  base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey> remappings;

  for (const auto& modifier_key : keyboard.modifier_keys) {
    if (modifier_key == ui::mojom::ModifierKey::kMeta) {
      // The meta key is handled separately.
      continue;
    }
    auto it = kKeyboardModifierMappings.find(modifier_key);
    DCHECK(it != kKeyboardModifierMappings.end());
    const auto pref_modifier_key =
        static_cast<ui::mojom::ModifierKey>(prefs->GetInteger(it->second));
    if (modifier_key != pref_modifier_key) {
      remappings.emplace(modifier_key, pref_modifier_key);
    }
  }

  const auto meta_key_pref_value = static_cast<ui::mojom::ModifierKey>(
      prefs->GetInteger(kMetaKeyMapping.at(keyboard.meta_key)));
  if (ui::mojom::ModifierKey::kMeta != meta_key_pref_value) {
    remappings.emplace(ui::mojom::ModifierKey::kMeta, meta_key_pref_value);
  }
  return remappings;
}

base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>
GetModifierRemappingsKnownUser(const user_manager::KnownUser& known_user,
                               const AccountId& account_id,
                               const mojom::Keyboard& keyboard) {
  base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey> remappings;

  for (const auto& modifier_key : keyboard.modifier_keys) {
    if (modifier_key == ui::mojom::ModifierKey::kMeta) {
      // The meta key is handled separately.
      continue;
    }
    auto it = kKeyboardModifierMappings.find(modifier_key);
    DCHECK(it != kKeyboardModifierMappings.end());
    const auto pref_modifier_key = static_cast<ui::mojom::ModifierKey>(
        known_user.FindIntPath(account_id, it->second)
            .value_or(static_cast<int>(modifier_key)));
    if (modifier_key != pref_modifier_key) {
      remappings.emplace(modifier_key, pref_modifier_key);
    }
  }

  const auto meta_key_pref_value = static_cast<ui::mojom::ModifierKey>(
      known_user.FindIntPath(account_id, kMetaKeyMapping.at(keyboard.meta_key))
          .value_or(static_cast<int>(ui::mojom::ModifierKey::kMeta)));
  if (ui::mojom::ModifierKey::kMeta != meta_key_pref_value) {
    remappings.emplace(ui::mojom::ModifierKey::kMeta, meta_key_pref_value);
  }
  return remappings;
}

mojom::KeyboardSettingsPtr GetKeyboardSettingsFromGlobalPrefs(
    PrefService* prefs,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard,
    ForceKeyboardSettingPersistence& force_persistence) {
  mojom::KeyboardSettingsPtr settings = mojom::KeyboardSettings::New();

  // For the transition period, since the default behavior changed for external
  // keyboards, the value from prefs must always be used even if the user did
  // not explicitly configure it. Users expect their settings to remain
  // consistent even if we think they may like the new default better.
  settings->top_row_are_fkeys = prefs->GetBoolean(prefs::kSendFunctionKeys);
  force_persistence.top_row_are_fkeys =
      prefs->GetUserPrefValue(prefs::kSendFunctionKeys) != nullptr;

  settings->suppress_meta_fkey_rewrites =
      GetDefaultSuppressMetaFkeyRewritesValue(keyboard_policies, keyboard);
  // Do not persist as default should not be persisted.
  force_persistence.suppress_meta_fkey_rewrites = false;

  settings->modifier_remappings = GetModifierRemappings(prefs, keyboard);

  if (ShouldAddSixPackKeyProperties(keyboard)) {
    settings->six_pack_key_remappings = GetSixPackKeyRemappings(prefs);
  }

  if (ShouldAddExtendedFkeyProperties(keyboard)) {
    settings->f11 = GetDefaultF11KeyValue(keyboard_policies, keyboard);
    settings->f12 = GetDefaultF12KeyValue(keyboard_policies, keyboard);
  }

  return settings;
}

mojom::SixPackKeyInfoPtr RetrieveSixPackRemappings(
    PrefService* pref_service,
    const base::Value::Dict& settings_dict) {
  const auto* six_pack_key_remappings_dict =
      settings_dict.FindDict(prefs::kKeyboardSettingSixPackKeyRemappings);
  if (!six_pack_key_remappings_dict) {
    return GetSixPackKeyRemappings(pref_service);
  } else {
    mojom::SixPackKeyInfoPtr six_pack_key_info = mojom::SixPackKeyInfo::New();
    six_pack_key_info->page_up = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyPageUp);
    six_pack_key_info->page_down = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyPageDown);
    six_pack_key_info->home = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyHome);
    six_pack_key_info->end = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyEnd);
    six_pack_key_info->del = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyDelete);
    six_pack_key_info->insert = GetSixPackShortcutModifierFromSettingsDict(
        *six_pack_key_remappings_dict, prefs::kSixPackKeyInsert);

    return six_pack_key_info;
  }
}

base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>
RetrieveModifierRemappings(const mojom::Keyboard& keyboard,
                           const base::Value::Dict& modifier_remappings_dict) {
  base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>
      modifier_remappings;

  for (const auto [from, to] : modifier_remappings_dict) {
    // `from` must be a string which can be converted to an int and `to` must be
    // an int.
    int from_int, to_int;
    if (!to.is_int() || !base::StringToInt(from, &from_int)) {
      LOG(ERROR) << "Unable to parse modifier remappings from prefs. From: "
                 << from << " To: " << to.DebugString();
      continue;
    }
    to_int = to.GetInt();

    // Validate the ints can be cast to `ui::mojom::ModifierKey` and cast them.
    if (!IsValidModifier(from_int) || !IsValidModifier(to_int)) {
      LOG(ERROR) << "Read invalid modifier keys from pref. From: " << from_int
                 << " To: " << to_int;
      continue;
    }
    const ui::mojom::ModifierKey from_key =
        static_cast<ui::mojom::ModifierKey>(from_int);
    const ui::mojom::ModifierKey to_key =
        static_cast<ui::mojom::ModifierKey>(to_int);
    if (from_key == to_key) {
      continue;
    }

    // Do not add modifier remappings for modifier keys that do not exist on the
    // given keyboard.
    if (!base::Contains(keyboard.modifier_keys, from_key)) {
      continue;
    }

    // Do not add modifier remappings for function key if function key is not a
    // modifier key.
    if (to_key == ui::mojom::ModifierKey::kFunction &&
        !base::Contains(keyboard.modifier_keys, to_key)) {
      continue;
    }

    modifier_remappings[from_key] = to_key;
  }

  if (keyboard.meta_key == ui::mojom::MetaKey::kCommand) {
    if (!modifier_remappings_dict.contains(base::NumberToString(
            static_cast<int>(ui::mojom::ModifierKey::kMeta)))) {
      modifier_remappings[ui::mojom::ModifierKey::kMeta] =
          ui::mojom::ModifierKey::kControl;
    }

    if (!modifier_remappings_dict.contains(base::NumberToString(
            static_cast<int>(ui::mojom::ModifierKey::kControl)))) {
      modifier_remappings[ui::mojom::ModifierKey::kControl] =
          ui::mojom::ModifierKey::kMeta;
    }
  }

  return modifier_remappings;
}

mojom::KeyboardSettingsPtr RetrieveKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard,
    const base::Value::Dict& settings_dict) {
  mojom::KeyboardSettingsPtr settings = mojom::KeyboardSettings::New();
  settings->suppress_meta_fkey_rewrites =
      settings_dict.FindBool(prefs::kKeyboardSettingSuppressMetaFKeyRewrites)
          .value_or(GetDefaultSuppressMetaFkeyRewritesValue(keyboard_policies,
                                                            keyboard));
  settings->top_row_are_fkeys =
      settings_dict.FindBool(prefs::kKeyboardSettingTopRowAreFKeys)
          .value_or(GetDefaultTopRowAreFKeysValue(keyboard_policies, keyboard));

  if (ShouldAddExtendedFkeyProperties(keyboard)) {
    settings->f11 =
        settings_dict.Find(prefs::kKeyboardSettingF11)
            ? static_cast<ui::mojom::ExtendedFkeysModifier>(
                  settings_dict.FindInt(prefs::kKeyboardSettingF11).value())
            : GetDefaultF11KeyValue(keyboard_policies, keyboard);

    settings->f12 =
        settings_dict.Find(prefs::kKeyboardSettingF12)
            ? static_cast<ui::mojom::ExtendedFkeysModifier>(
                  settings_dict.FindInt(prefs::kKeyboardSettingF12).value())
            : GetDefaultF12KeyValue(keyboard_policies, keyboard);
  }

  const auto* modifier_remappings_dict =
      settings_dict.FindDict(prefs::kKeyboardSettingModifierRemappings);
  if (modifier_remappings_dict) {
    settings->modifier_remappings =
        RetrieveModifierRemappings(keyboard, *modifier_remappings_dict);
  } else {
    settings->modifier_remappings =
        RetrieveModifierRemappings(keyboard, /*modifier_remappings_dict=*/{});
  }

  if (ShouldAddSixPackKeyProperties(keyboard)) {
    settings->six_pack_key_remappings =
        RetrieveSixPackRemappings(pref_service, settings_dict);
  }

  return settings;
}

mojom::KeyboardSettingsPtr GetDefaultKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  if (pref_service) {
    const auto& default_settings_dict =
        pref_service->GetDict(GetDefaultKeyboardPref(keyboard));
    return RetrieveKeyboardSettings(pref_service, keyboard_policies, keyboard,
                                    default_settings_dict);
  }

  base::Value::Dict settings_dict;
  if (Shell::Get()->keyboard_capability()->HasRightAltKeyForOobe(keyboard.id)) {
    base::Value::Dict modifier_remappings_dict;
    modifier_remappings_dict.Set(
        base::NumberToString(
            static_cast<int>(ui::mojom::ModifierKey::kAssistant)),
        static_cast<int>(ui::mojom::ModifierKey::kCapsLock));
    settings_dict.Set(prefs::kKeyboardSettingModifierRemappings,
                      std::move(modifier_remappings_dict));
  }

  return RetrieveKeyboardSettings(pref_service, keyboard_policies, keyboard,
                                  std::move(settings_dict));
}

base::Value::Dict ConvertModifierRemappingsToDict(
    const mojom::Keyboard& keyboard) {
  // Modifier remappings get stored in a dict by casting the
  // `ui::mojom::ModifierKey` enum to ints. Since `base::Value::Dict` only
  // supports strings as keys, this is then converted into a string.
  base::Value::Dict modifier_remappings;
  for (const auto& [from, to] : keyboard.settings->modifier_remappings) {
    // Avoid saving modifier remappings that are default for apple keyboards.
    if (keyboard.meta_key == ui::mojom::MetaKey::kCommand &&
        IsAppleKeyboardDefaultModifierRemapping(from, to)) {
      continue;
    }

    modifier_remappings.Set(base::NumberToString(static_cast<int>(from)),
                            static_cast<int>(to));
  }

  // Since Apple keyboards default remaps Meta -> Control and Control -> Meta,
  // this must be taken in to account when saving prefs so we store them when
  // they are non-default.
  if (keyboard.meta_key == ui::mojom::MetaKey::kCommand) {
    if (!keyboard.settings->modifier_remappings.contains(
            ui::mojom::ModifierKey::kMeta)) {
      modifier_remappings.Set(
          base::NumberToString(static_cast<int>(ui::mojom::ModifierKey::kMeta)),
          static_cast<int>(ui::mojom::ModifierKey::kMeta));
    }

    if (!keyboard.settings->modifier_remappings.contains(
            ui::mojom::ModifierKey::kControl)) {
      modifier_remappings.Set(
          base::NumberToString(
              static_cast<int>(ui::mojom::ModifierKey::kControl)),
          static_cast<int>(ui::mojom::ModifierKey::kControl));
    }
  }

  return modifier_remappings;
}

base::Value::Dict ConvertSettingsToDict(
    const mojom::Keyboard& keyboard,
    const mojom::KeyboardPolicies& keyboard_policies,
    const ForceKeyboardSettingPersistence& force_persistence,
    const base::Value::Dict* existing_settings_dict) {
  // Populate `settings_dict` with all settings in `settings`.
  base::Value::Dict settings_dict;

  if (ShouldPersistSetting(
          prefs::kKeyboardSettingSuppressMetaFKeyRewrites,
          keyboard.settings->suppress_meta_fkey_rewrites,
          GetDefaultSuppressMetaFkeyRewritesValue(keyboard_policies, keyboard),
          force_persistence.suppress_meta_fkey_rewrites,
          existing_settings_dict)) {
    settings_dict.Set(prefs::kKeyboardSettingSuppressMetaFKeyRewrites,
                      keyboard.settings->suppress_meta_fkey_rewrites);
  }

  if (ShouldPersistSetting(
          keyboard_policies.top_row_are_fkeys_policy,
          prefs::kKeyboardSettingTopRowAreFKeys,
          keyboard.settings->top_row_are_fkeys,
          GetDefaultTopRowAreFKeysValue(keyboard_policies, keyboard),
          force_persistence.top_row_are_fkeys, existing_settings_dict)) {
    settings_dict.Set(prefs::kKeyboardSettingTopRowAreFKeys,
                      keyboard.settings->top_row_are_fkeys);
  }

  if (ShouldAddExtendedFkeyProperties(keyboard)) {
    if (ShouldPersistFkeySetting(
            keyboard_policies.f11_key_policy, prefs::kKeyboardSettingF11,
            keyboard.settings->f11,
            GetDefaultF11KeyValue(keyboard_policies, keyboard),
            existing_settings_dict)) {
      settings_dict.Set(prefs::kKeyboardSettingF11,
                        static_cast<int>(keyboard.settings->f11.value()));
    }
    if (ShouldPersistFkeySetting(
            keyboard_policies.f12_key_policy, prefs::kKeyboardSettingF12,
            keyboard.settings->f12,
            GetDefaultF12KeyValue(keyboard_policies, keyboard),
            existing_settings_dict)) {
      settings_dict.Set(prefs::kKeyboardSettingF12,
                        static_cast<int>(keyboard.settings->f12.value()));
    }
  }

  if (ShouldAddSixPackKeyProperties(keyboard)) {
    base::Value::Dict six_pack_key_remappings;
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyPageUp,
        static_cast<int>(keyboard.settings->six_pack_key_remappings->page_up));
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyPageDown,
        static_cast<int>(
            keyboard.settings->six_pack_key_remappings->page_down));
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyHome,
        static_cast<int>(keyboard.settings->six_pack_key_remappings->home));
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyEnd,
        static_cast<int>(keyboard.settings->six_pack_key_remappings->end));
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyDelete,
        static_cast<int>(keyboard.settings->six_pack_key_remappings->del));
    six_pack_key_remappings.Set(
        prefs::kSixPackKeyInsert,
        static_cast<int>(keyboard.settings->six_pack_key_remappings->insert));
    settings_dict.Set(prefs::kKeyboardSettingSixPackKeyRemappings,
                      std::move(six_pack_key_remappings));
  }

  settings_dict.Set(prefs::kKeyboardSettingModifierRemappings,
                    ConvertModifierRemappingsToDict(keyboard));
  return settings_dict;
}

void UpdateInternalKeyboardSettingsImpl(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard,
    const ForceKeyboardSettingPersistence& force_persistence) {
  CHECK(keyboard.settings);
  CHECK(!keyboard.is_external);

  base::Value::Dict existing_settings_dict =
      pref_service->GetDict(prefs::kKeyboardInternalSettings).Clone();
  base::Value::Dict settings_dict = ConvertSettingsToDict(
      keyboard, keyboard_policies, force_persistence, &existing_settings_dict);

  // Merge all settings except modifier remappings. Modifier remappings need
  // to overwrite what was previously stored.
  auto modifier_remappings_dict =
      settings_dict.Extract(prefs::kKeyboardSettingModifierRemappings);
  existing_settings_dict.Merge(std::move(settings_dict));
  existing_settings_dict.Set(prefs::kKeyboardSettingModifierRemappings,
                             std::move(*modifier_remappings_dict));

  pref_service->SetDict(prefs::kKeyboardInternalSettings,
                        std::move(existing_settings_dict));
}

void UpdateKeyboardSettingsImpl(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard,
    const ForceKeyboardSettingPersistence& force_persistence) {
  DCHECK(keyboard.settings);

  if (!keyboard.is_external) {
    UpdateInternalKeyboardSettingsImpl(pref_service, keyboard_policies,
                                       keyboard, force_persistence);
    return;
  }

  base::Value::Dict devices_dict =
      pref_service->GetDict(prefs::kKeyboardDeviceSettingsDictPref).Clone();
  base::Value::Dict* existing_settings_dict =
      devices_dict.FindDict(keyboard.device_key);
  base::Value::Dict settings_dict = ConvertSettingsToDict(
      keyboard, keyboard_policies, force_persistence, existing_settings_dict);
  const base::Time time_stamp = base::Time::Now();
  settings_dict.Set(prefs::kLastUpdatedKey, base::TimeToValue(time_stamp));

  if (existing_settings_dict) {
    // Merge all settings except modifier remappings. Modifier remappings need
    // to overwrite what was previously stored.
    auto modifier_remappings_dict =
        settings_dict.Extract(prefs::kKeyboardSettingModifierRemappings);
    existing_settings_dict->Merge(std::move(settings_dict));
    existing_settings_dict->Set(prefs::kKeyboardSettingModifierRemappings,
                                std::move(*modifier_remappings_dict));
    if (ShouldAddSixPackKeyProperties(keyboard)) {
      // 6-pack key remappings need to overwrite what was previously stored.
      auto six_pack_key_remappings_dict =
          settings_dict.Extract(prefs::kKeyboardSettingSixPackKeyRemappings);
      if (six_pack_key_remappings_dict.has_value()) {
        existing_settings_dict->Set(prefs::kKeyboardSettingSixPackKeyRemappings,
                                    std::move(*six_pack_key_remappings_dict));
      }
    }

  } else {
    devices_dict.Set(keyboard.device_key, std::move(settings_dict));
  }

  pref_service->SetDict(std::string(prefs::kKeyboardDeviceSettingsDictPref),
                        std::move(devices_dict));
}

mojom::KeyboardSettingsPtr GetKeyboardSettingsFromOldLocalStatePrefs(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard& keyboard) {
  mojom::KeyboardSettingsPtr settings = GetDefaultKeyboardSettings(
      /*pref_service=*/nullptr, keyboard_policies, keyboard);

  settings->modifier_remappings = GetModifierRemappingsKnownUser(
      user_manager::KnownUser(local_state), account_id, keyboard);

  return settings;
}

bool HasDefaultSettings(PrefService* pref_service,
                        const mojom::Keyboard& keyboard) {
  const auto* pref =
      pref_service->FindPreference(GetDefaultKeyboardPref(keyboard));
  return pref && pref->HasUserSetting();
}

void InitializeSettingsUpdateMetricInfo(
    PrefService* pref_service,
    const mojom::Keyboard& keyboard,
    SettingsUpdatedMetricsInfo::Category category) {
  CHECK(pref_service);

  const auto& settings_metric_info =
      pref_service->GetDict(prefs::kKeyboardUpdateSettingsMetricInfo);
  const auto* device_metric_info =
      settings_metric_info.Find(keyboard.device_key);
  if (device_metric_info) {
    return;
  }

  auto updated_metric_info = settings_metric_info.Clone();

  const SettingsUpdatedMetricsInfo metrics_info(category, base::Time::Now());
  updated_metric_info.Set(keyboard.device_key, metrics_info.ToDict());

  pref_service->SetDict(prefs::kKeyboardUpdateSettingsMetricInfo,
                        std::move(updated_metric_info));
}

void InitializeKeyboardSettingsImpl(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard* keyboard,
    bool force_initialize_to_default_settings) {
  if (!pref_service) {
    keyboard->settings =
        GetDefaultKeyboardSettings(pref_service, keyboard_policies, *keyboard);
    return;
  }

  const base::Value::Dict* settings_dict = nullptr;
  if (!keyboard->is_external) {
    settings_dict = &pref_service->GetDict(prefs::kKeyboardInternalSettings);
    if (settings_dict->empty()) {
      settings_dict = nullptr;
    }
  } else {
    const auto& devices_dict =
        pref_service->GetDict(prefs::kKeyboardDeviceSettingsDictPref);
    settings_dict = devices_dict.FindDict(keyboard->device_key);
  }

  // Do not lookup settings dict if we are force refreshing back to default
  // settings.
  if (force_initialize_to_default_settings) {
    settings_dict = nullptr;
  }

  ForceKeyboardSettingPersistence force_persistence;
  SettingsUpdatedMetricsInfo::Category category;
  if (settings_dict) {
    category = SettingsUpdatedMetricsInfo::Category::kSynced;
    keyboard->settings = RetrieveKeyboardSettings(
        pref_service, keyboard_policies, *keyboard, *settings_dict);
  } else if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
                 InputDeviceTracker::InputDeviceCategory::kKeyboard,
                 keyboard->device_key)) {
    category = SettingsUpdatedMetricsInfo::Category::kDefault;
    keyboard->settings = GetKeyboardSettingsFromGlobalPrefs(
        pref_service, keyboard_policies, *keyboard, force_persistence);
  } else {
    keyboard->settings =
        GetDefaultKeyboardSettings(pref_service, keyboard_policies, *keyboard);
    category = HasDefaultSettings(pref_service, *keyboard)
                   ? SettingsUpdatedMetricsInfo::Category::kDefault
                   : SettingsUpdatedMetricsInfo::Category::kFirstEver;
  }
  DCHECK(keyboard->settings);
  InitializeSettingsUpdateMetricInfo(pref_service, *keyboard, category);

  UpdateKeyboardSettingsImpl(pref_service, keyboard_policies, *keyboard,
                             force_persistence);

  if (keyboard_policies.top_row_are_fkeys_policy &&
      keyboard_policies.top_row_are_fkeys_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    keyboard->settings->top_row_are_fkeys =
        keyboard_policies.top_row_are_fkeys_policy->value;
  }

  if (keyboard->is_external &&
      keyboard_policies.enable_meta_fkey_rewrites_policy &&
      keyboard_policies.enable_meta_fkey_rewrites_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    // Invert the value of the policy when saving the setting, because the
    // policy determines whether meta fkey rewrites are enabled, and the setting
    // controls whether meta fkey rewrites are disabled.
    keyboard->settings->suppress_meta_fkey_rewrites =
        !keyboard_policies.enable_meta_fkey_rewrites_policy->value;
  }

  if (ShouldAddExtendedFkeyProperties(*keyboard)) {
    if (keyboard_policies.f11_key_policy &&
        keyboard_policies.f11_key_policy->policy_status ==
            mojom::PolicyStatus::kManaged) {
      keyboard->settings->f11 = keyboard_policies.f11_key_policy->value;
    }

    if (keyboard_policies.f12_key_policy &&
        keyboard_policies.f12_key_policy->policy_status ==
            mojom::PolicyStatus::kManaged) {
      keyboard->settings->f12 = keyboard_policies.f12_key_policy->value;
    }
  }
  if (keyboard_policies.home_and_end_keys_policy &&
      keyboard_policies.home_and_end_keys_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    keyboard->settings->six_pack_key_remappings->home =
        keyboard_policies.home_and_end_keys_policy->value;
    keyboard->settings->six_pack_key_remappings->end =
        keyboard_policies.home_and_end_keys_policy->value;
  }

  if (keyboard_policies.page_up_and_page_down_keys_policy &&
      keyboard_policies.page_up_and_page_down_keys_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    keyboard->settings->six_pack_key_remappings->page_up =
        keyboard_policies.page_up_and_page_down_keys_policy->value;
    keyboard->settings->six_pack_key_remappings->page_down =
        keyboard_policies.page_up_and_page_down_keys_policy->value;
  }

  if (keyboard_policies.delete_key_policy &&
      keyboard_policies.delete_key_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    keyboard->settings->six_pack_key_remappings->del =
        keyboard_policies.delete_key_policy->value;
  }

  if (keyboard_policies.insert_key_policy &&
      keyboard_policies.insert_key_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    keyboard->settings->six_pack_key_remappings->insert =
        keyboard_policies.insert_key_policy->value;
  }
}

}  // namespace

KeyboardPrefHandlerImpl::KeyboardPrefHandlerImpl() = default;
KeyboardPrefHandlerImpl::~KeyboardPrefHandlerImpl() = default;

void KeyboardPrefHandlerImpl::InitializeKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard* keyboard) {
  InitializeKeyboardSettingsImpl(
      pref_service, keyboard_policies, keyboard,
      /*force_initialize_to_default_settings=*/false);
}

void KeyboardPrefHandlerImpl::InitializeLoginScreenKeyboardSettings(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard* keyboard) {
  // Verify if the flag is enabled.
  if (!features::IsInputDeviceSettingsSplitEnabled()) {
    return;
  }
  CHECK(local_state);

  const auto* settings_dict = GetLoginScreenSettingsDict(
      local_state, account_id,
      keyboard->is_external ? prefs::kKeyboardLoginScreenExternalSettingsPref
                            : prefs::kKeyboardLoginScreenInternalSettingsPref);
  if (settings_dict) {
    keyboard->settings = RetrieveKeyboardSettings(
        /*pref_service=*/nullptr, keyboard_policies, *keyboard, *settings_dict);
  } else {
    keyboard->settings = GetKeyboardSettingsFromOldLocalStatePrefs(
        local_state, account_id, keyboard_policies, *keyboard);
  }

  if (ShouldAddSixPackKeyProperties(*keyboard)) {
    keyboard->settings->six_pack_key_remappings = mojom::SixPackKeyInfo::New();
  }
}

void KeyboardPrefHandlerImpl::UpdateKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  UpdateKeyboardSettingsImpl(pref_service, keyboard_policies, keyboard,
                             /*force_persistence=*/{});
}

void KeyboardPrefHandlerImpl::UpdateLoginScreenKeyboardSettings(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  CHECK(local_state);
  const auto* pref_name = keyboard.is_external
                              ? prefs::kKeyboardLoginScreenExternalSettingsPref
                              : prefs::kKeyboardLoginScreenInternalSettingsPref;
  auto* settings_dict =
      GetLoginScreenSettingsDict(local_state, account_id, pref_name);
  user_manager::KnownUser(local_state)
      .SetPath(account_id, pref_name,
               std::make_optional<base::Value>(ConvertSettingsToDict(
                   keyboard, keyboard_policies, /*force_persistence=*/{},
                   settings_dict)));
}

void KeyboardPrefHandlerImpl::InitializeWithDefaultKeyboardSettings(
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard* keyboard) {
  keyboard->settings = GetDefaultKeyboardSettings(/*pref_service=*/nullptr,
                                                  keyboard_policies, *keyboard);
}

void KeyboardPrefHandlerImpl::UpdateDefaultChromeOSKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  CHECK(IsChromeOSKeyboard(keyboard));
  // All settings should be persisted fully when storing defaults.
  auto settings_dict = ConvertSettingsToDict(
      keyboard, keyboard_policies, /*force_persistence=*/{true},
      /*existing_settings_dict=*/nullptr);
  pref_service->SetDict(prefs::kKeyboardDefaultChromeOSSettings,
                        std::move(settings_dict));
}

void KeyboardPrefHandlerImpl::UpdateDefaultNonChromeOSKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  CHECK(!IsChromeOSKeyboard(keyboard));
  // All settings should be persisted fully when storing defaults.
  auto settings_dict = ConvertSettingsToDict(
      keyboard, keyboard_policies, /*force_persistence=*/{true},
      /*existing_settings_dict=*/nullptr);
  pref_service->SetDict(prefs::kKeyboardDefaultNonChromeOSSettings,
                        std::move(settings_dict));
}

void KeyboardPrefHandlerImpl::UpdateDefaultSplitModifierKeyboardSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    const mojom::Keyboard& keyboard) {
  CHECK(IsSplitModifierKeyboard(keyboard));

  // All settings should be persisted fully when storing defaults.
  auto settings_dict = ConvertSettingsToDict(
      keyboard, keyboard_policies, /*force_persistence=*/{true},
      /*existing_settings_dict=*/nullptr);
  pref_service->SetDict(prefs::kKeyboardDefaultSplitModifierSettings,
                        std::move(settings_dict));
}

void KeyboardPrefHandlerImpl::ForceInitializeWithDefaultSettings(
    PrefService* pref_service,
    const mojom::KeyboardPolicies& keyboard_policies,
    mojom::Keyboard* keyboard) {
  InitializeKeyboardSettingsImpl(pref_service, keyboard_policies, keyboard,
                                 /*force_initialize_to_default_settings=*/true);
}

}  // namespace ash