chromium/ash/system/input_device_settings/input_device_settings_metrics_manager.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.

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

#include "ash/system/input_device_settings/input_device_settings_metrics_manager.h"

#include <cstdint>
#include <iterator>
#include <optional>
#include <string_view>

#include "ash/accelerators/accelerator_encoding.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom-shared.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_controller_impl.h"
#include "ash/system/input_device_settings/input_device_settings_metadata.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/settings_updated_metrics_info.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "base/json/values_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "components/prefs/pref_service.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/keyboard_info_metrics.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/evdev/keyboard_mouse_combo_device_metrics.h"

namespace ash {

namespace {

enum class PointerSensitivity {
  kLowest = 1,
  kLow = 2,
  kMedium = 3,
  kHigh = 4,
  kHighest = 5,
  kMaxValue = kHighest,
};

// Do not change ordering of this list as the ordering is used to compute
// modifier hash in `RecordModifierRemappingHash()`.
// TODO(b/329330990): Update modifier names map.
static constexpr struct {
  const char* key_name;
  ui::mojom::ModifierKey modifier_key;
} kModifierNames[] = {
    {"Meta", ui::mojom::ModifierKey::kMeta},
    {"Control", ui::mojom::ModifierKey::kControl},
    {"Alt", ui::mojom::ModifierKey::kAlt},
    {"CapsLock", ui::mojom::ModifierKey::kCapsLock},
    {"Escape", ui::mojom::ModifierKey::kEscape},
    {"Backspace", ui::mojom::ModifierKey::kBackspace},
    {"Assistant", ui::mojom::ModifierKey::kAssistant},
    {"Function", ui::mojom::ModifierKey::kFunction},
    {"RightAlt", ui::mojom::ModifierKey::kRightAlt},
};

// The modifier hash is made up of `kNumModifiers` blocks of
// `kModifierHashWidth` bits. Each modifier is assigned a `kModifierHashWidth`
// width block to track its user configured setting. These user configured
// settings are contained within [0, `kMaxModifierValue`] and are assigned in
// /ash/public/input_device_settings.mojom in the `mojom::ModifierKey` struct.

// To decode, break up the hash into `kModifierHashWidth` bit integers.
// For example, if `kModifierHashWidth` is 4, use the following bit ranges to
// extract the value of the remapped modifier:

// | index | ModifierKey             | Bit Range |
// | 0     | kMeta                   | [0, 3]    |
// | 1     | kControl                | [4, 7]    |
// | 2     | kAlt                    | [8, 11]   |
// | 3     | kCapsLock               | [12, 15]  |
// | 4     | kEscape                 | [16, 19]  |
// | 5     | kBackspace              | [20, 23]  |
// | 6     | kAssistant | kRightAlt  | [24, 27]  |
// | 7     | kFunction               | [28, 31]  |

// Each modifier key will have 9 actions which requires 4 bits to encode.
constexpr int kModifierHashWidth = 4;
constexpr int kMaxModifierValue = (1 << kModifierHashWidth) - 1;

// Remove Function and RightAlt for regular keyboards.
constexpr int kNumModifiers = std::size(kModifierNames) - 2;
// Remove RightAlt for split modifier keyboard since it has the same domcode as
// Assistant.
constexpr int kSplitModifierNumModifiers = std::size(kModifierNames) - 1;

// Verify that the number of modifiers we are trying to hash together into a
// 32-bit int will fit without any overflow or UB.
// Modifier hash is limited to 32 bits as metrics can only handle 32 bit ints.
// TODO(b/329330990): Update modifier hash.
static_assert((sizeof(int32_t) * 8) >= (kModifierHashWidth * kNumModifiers));
static_assert(static_cast<int>(ui::mojom::ModifierKey::kMaxValue) <=
              kMaxModifierValue);

// Precomputes the value of the modifier hash when all prefs are configured to
// their default value.
constexpr int32_t PrecalculateDefaultModifierHash() {
  uint32_t hash = 0;
  for (ssize_t i = kNumModifiers - 1u; i >= 0; i--) {
    hash <<= kModifierHashWidth;
    hash += static_cast<int>(kModifierNames[i].modifier_key);
  }
  return hash;
}
constexpr int32_t kDefaultModifierHash = PrecalculateDefaultModifierHash();

constexpr uint32_t PrecalculateSplitModifierDefaultModifierHash() {
  uint32_t hash = 0;
  for (ssize_t i = kSplitModifierNumModifiers - 1u; i >= 0; i--) {
    hash <<= kModifierHashWidth;
    if (kModifierNames[i].modifier_key == ui::mojom::ModifierKey::kAssistant) {
      hash += static_cast<int>(ui::mojom::ModifierKey::kRightAlt);
    } else {
      hash += static_cast<int>(kModifierNames[i].modifier_key);
    }
  }
  return hash;
}
constexpr uint32_t kSplitModifierDefaultModifierHash =
    PrecalculateSplitModifierDefaultModifierHash();

std::string GetKeyboardMetricsPrefix(const mojom::Keyboard& keyboard) {
  if (!keyboard.is_external) {
    return "ChromeOS.Settings.Device.Keyboard.Internal.";
  } else if (keyboard.meta_key == ui::mojom::MetaKey::kLauncherRefresh ||
             keyboard.meta_key == ui::mojom::MetaKey::kLauncher ||
             keyboard.meta_key == ui::mojom::MetaKey::kSearch) {
    return "ChromeOS.Settings.Device.Keyboard.ExternalChromeOS.";
  } else {
    return "ChromeOS.Settings.Device.Keyboard.External.";
  }
}

std::string_view ToMetricsString(
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  switch (peripheral_kind) {
    case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
        kMouse:
      return "Mouse";
    case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
        kGraphicsTablet:
      return "GraphicsTablet";
    case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
        kGraphicsTabletPen:
      return "GraphicsTabletPen";
  }
}

ui::mojom::ModifierKey GetModifierRemappingTo(
    const mojom::KeyboardSettings& settings,
    ui::mojom::ModifierKey modifier_key) {
  const auto iter = settings.modifier_remappings.find(modifier_key);
  if (iter != settings.modifier_remappings.end()) {
    return iter->second;
  }
  return modifier_key;
}

std::string GetModifierKeyName(ui::mojom::ModifierKey modifier_key) {
  for (const auto& modifier : kModifierNames) {
    if (modifier.modifier_key == modifier_key) {
      return modifier.key_name;
    }
  }
  NOTREACHED() << "MODIFIER KEY: " << (int)modifier_key;
}

int GetNumberOfNonDefaultRemappings(
    const mojom::KeyboardSettings& settings,
    const base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>&
        default_remappings) {
  int num_keys_changed = 0;
  const auto& remappings = settings.modifier_remappings;

  // Count the number of different pairs in the current remapping that is not
  // default. For defaults on apple keyboard, it would be a flat_map:
  // {{ModifierKey.Meta => ModifierKey.Control},
  //  {ModifierKey.Control => ModifierKey.Meta}},
  // For others, it's an empty flat_map.
  // A remapping pair [remapped_from, remapped_to] in current remapping is
  // considered to be non-default if:
  //   1. remapped_from does not exist in the default remapping.
  //   2. remapped_from is mapped to any key other than its default mapping.
  for (const auto& [remapped_from, remapped_to] : remappings) {
    const auto& iter = default_remappings.find(remapped_from);
    if (iter == default_remappings.end() || iter->second != remapped_to) {
      ++num_keys_changed;
    }
  }
  for (const auto& [remapped_from, remapped_to] : default_remappings) {
    if (!remappings.contains(remapped_from)) {
      ++num_keys_changed;
    }
  }
  return num_keys_changed;
}

ui::mojom::SixPackShortcutModifier GetSixPackKeyModifier(
    const mojom::Keyboard& keyboard,
    ui::KeyboardCode key_code) {
  CHECK(ui::KeyboardCapability::IsSixPackKey(key_code));
  CHECK(keyboard.settings->six_pack_key_remappings);
  switch (key_code) {
    case ui::VKEY_DELETE:
      return keyboard.settings->six_pack_key_remappings->del;
    case ui::VKEY_INSERT:
      return keyboard.settings->six_pack_key_remappings->insert;
    case ui::VKEY_HOME:
      return keyboard.settings->six_pack_key_remappings->home;
    case ui::VKEY_END:
      return keyboard.settings->six_pack_key_remappings->end;
    case ui::VKEY_PRIOR:
      return keyboard.settings->six_pack_key_remappings->page_up;
    case ui::VKEY_NEXT:
      return keyboard.settings->six_pack_key_remappings->page_down;
    default:
      NOTREACHED();
  }
}
std::string GetSixPackKeyMetricName(const std::string& prefix,
                                    ui::KeyboardCode key_code,
                                    bool is_initial_value) {
  CHECK(ui::KeyboardCapability::IsSixPackKey(key_code));
  std::string key_name;
  switch (key_code) {
    case ui::VKEY_DELETE:
      key_name = "Delete";
      break;
    case ui::VKEY_INSERT:
      key_name = "Insert";
      break;
    case ui::VKEY_HOME:
      key_name = "Home";
      break;
    case ui::VKEY_END:
      key_name = "End";
      break;
    case ui::VKEY_PRIOR:
      key_name = "PageUp";
      break;
    case ui::VKEY_NEXT:
      key_name = "PageDown";
      break;
    default:
      NOTREACHED();
  }
  return base::StrCat({prefix, "SixPackKeys.", key_name,
                       is_initial_value ? ".Initial" : ".Changed"});
}

void RecordKeyboardNumberOfKeysRemapped(const mojom::Keyboard& keyboard) {
  base::flat_map<ui::mojom::ModifierKey, ui::mojom::ModifierKey>
      default_remappings;
  if (keyboard.meta_key == ui::mojom::MetaKey::kCommand) {
    default_remappings[ui::mojom::ModifierKey::kControl] =
        ui::mojom::ModifierKey::kMeta;
    default_remappings[ui::mojom::ModifierKey::kMeta] =
        ui::mojom::ModifierKey::kControl;
  }
  const int num_keys_remapped = GetNumberOfNonDefaultRemappings(
      *keyboard.settings, std::move(default_remappings));
  const std::string keyboard_metrics =
      base::StrCat({GetKeyboardMetricsPrefix(keyboard),
                    "Modifiers.NumberOfRemappedKeysOnStart"});
  base::UmaHistogramCounts100(keyboard_metrics, num_keys_remapped);
}

bool ShouldRecordFkeyMetrics(const mojom::Keyboard& keyboard) {
  return ::features::AreF11AndF12ShortcutsEnabled() &&
         Shell::Get()->keyboard_capability()->IsChromeOSKeyboard(keyboard.id) &&
         (keyboard.settings->f11.has_value() &&
          keyboard.settings->f12.has_value()) &&
         !base::Contains(keyboard.modifier_keys,
                         ui::mojom::ModifierKey::kFunction);
}

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

void RecordButtonMetrics(const mojom::Button& button,
                         const std::string* metric_name_prefix) {
  if (button.is_customizable_button()) {
    base::UmaHistogramEnumeration(
        base::StrCat({*metric_name_prefix, "CustomizableButton"}),
        button.get_customizable_button());
  } else if (button.is_vkey()) {
    base::UmaHistogramSparse(base::StrCat({*metric_name_prefix, "Vkey"}),
                             button.get_vkey());
  }
}

void RecordButtonRemappingNameIfChanged(
    const mojom::ButtonRemappingPtr& original_remapping,
    const mojom::ButtonRemappingPtr& new_remapping,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  const std::string metric_name_prefix = base::StrCat(
      {"ChromeOS.Settings.Device.", ToMetricsString(peripheral_kind),
       ".ButtonRemapping.Name.Changed."});
  if (original_remapping->name != new_remapping->name) {
    RecordButtonMetrics(*(original_remapping->button), &metric_name_prefix);
  }
}

template <typename T>
std::string_view GetDeviceTypeMetricsName() {
  if constexpr (std::is_same_v<T, mojom::Keyboard>) {
    return "Keyboard";
  } else if constexpr (std::is_same_v<T, mojom::Mouse>) {
    return "Mouse";
  } else if constexpr (std::is_same_v<T, mojom::Touchpad>) {
    return "Touchpad";
  } else if constexpr (std::is_same_v<T, mojom::PointingStick>) {
    return "PointingStick";
  }
}

template <typename T>
std::string_view GetSettingsUpdatedPrefName() {
  if constexpr (std::is_same_v<T, mojom::Keyboard>) {
    return prefs::kKeyboardUpdateSettingsMetricInfo;
  } else if constexpr (std::is_same_v<T, mojom::Mouse>) {
    return prefs::kMouseUpdateSettingsMetricInfo;
  } else if constexpr (std::is_same_v<T, mojom::Touchpad>) {
    return prefs::kTouchpadUpdateSettingsMetricInfo;
  } else if constexpr (std::is_same_v<T, mojom::PointingStick>) {
    return prefs::kPointingStickUpdateSettingsMetricInfo;
  }
}

std::string_view GetSettingsUpdatedTimePeriodMetricName(
    SettingsUpdatedMetricsInfo::TimePeriod time_period) {
  constexpr auto kTimePeriodToMetricName =
      base::MakeFixedFlatMap<SettingsUpdatedMetricsInfo::TimePeriod,
                             std::string_view>({
          {SettingsUpdatedMetricsInfo::TimePeriod::kOneHour, "OneHour"},
          {SettingsUpdatedMetricsInfo::TimePeriod::kThreeHours, "ThreeHours"},
          {SettingsUpdatedMetricsInfo::TimePeriod::kOneDay, "OneDay"},
          {SettingsUpdatedMetricsInfo::TimePeriod::kThreeDays, "ThreeDays"},
          {SettingsUpdatedMetricsInfo::TimePeriod::kOneWeek, "OneWeek"},
      });
  return kTimePeriodToMetricName.at(time_period);
}

std::string_view GetSettingsUpdatedCategoryName(
    SettingsUpdatedMetricsInfo::Category category) {
  constexpr auto kCategoryToMetricName =
      base::MakeFixedFlatMap<SettingsUpdatedMetricsInfo::Category,
                             std::string_view>({
          {SettingsUpdatedMetricsInfo::Category::kFirstEver, "FirstEver"},
          {SettingsUpdatedMetricsInfo::Category::kDefault, "FromDefaults"},
          {SettingsUpdatedMetricsInfo::Category::kSynced, "Synced"},
      });
  return kCategoryToMetricName.at(category);
}

template <typename T>
void RecordSettingsUpdatedMetric(
    const T& device,
    const SettingsUpdatedMetricsInfo& metrics_info,
    SettingsUpdatedMetricsInfo::TimePeriod time_period_to_record) {
  const std::string metric_name = base::StrCat(
      {"ChromeOS.Settings.Device.", GetDeviceTypeMetricsName<T>(),
       ".SettingsUpdated.",
       GetSettingsUpdatedCategoryName(metrics_info.category()), ".",
       GetSettingsUpdatedTimePeriodMetricName(time_period_to_record)});
  base::UmaHistogramCounts100(metric_name,
                              metrics_info.GetCount(time_period_to_record));
}

template <typename T>
void HandleSettingsUpdatedMetric(const T& device) {
  PrefService* pref_service =
      Shell::Get()->session_controller()->GetActivePrefService();
  CHECK(pref_service);

  const auto& settings_update_info_dict =
      pref_service->GetDict(GetSettingsUpdatedPrefName<T>());
  const auto* device_settings_update_info_dict =
      settings_update_info_dict.FindDict(device.device_key);
  if (!device_settings_update_info_dict) {
    return;
  }

  std::optional<SettingsUpdatedMetricsInfo> metrics_info_optional =
      SettingsUpdatedMetricsInfo::FromDict(*device_settings_update_info_dict);
  if (!metrics_info_optional) {
    return;
  }

  SettingsUpdatedMetricsInfo& metrics_info = *metrics_info_optional;
  auto time_period = metrics_info.RecordSettingsUpdate(base::Time::Now());
  if (time_period) {
    RecordSettingsUpdatedMetric(device, metrics_info, *time_period);
  }

  auto updated_settings_update_info_dict = settings_update_info_dict.Clone();
  updated_settings_update_info_dict.Set(device.device_key,
                                        metrics_info.ToDict());
  pref_service->SetDict(std::string(GetSettingsUpdatedPrefName<T>()),
                        std::move(updated_settings_update_info_dict));
}

void RecordButtonRemappingAction(
    const mojom::RemappingAction& remapping_action,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind,
    const char* metric_name_suffix) {
  const std::string metric_name_prefix =
      base::StrCat({"ChromeOS.Settings.Device.",
                    ToMetricsString(peripheral_kind), ".ButtonRemapping."});
  switch (remapping_action.which()) {
    case mojom::RemappingAction::Tag::kAcceleratorAction:
      base::UmaHistogramSparse(
          base::StrCat(
              {metric_name_prefix, "AcceleratorAction.", metric_name_suffix}),
          remapping_action.get_accelerator_action());
      break;
    case mojom::RemappingAction::Tag::kStaticShortcutAction:
      base::UmaHistogramEnumeration(
          base::StrCat({metric_name_prefix, "StaticShortcutAction.",
                        metric_name_suffix}),
          remapping_action.get_static_shortcut_action());
      break;
    case mojom::RemappingAction::Tag::kKeyEvent:
      base::UmaHistogramSparse(
          base::StrCat({metric_name_prefix, "KeyEvent.", metric_name_suffix}),
          GetEncodedShortcut(remapping_action.get_key_event()->modifiers,
                             remapping_action.get_key_event()->vkey));
      break;
  }
}

void RecordButtonRemappingActionIfChanged(
    const mojom::ButtonRemappingPtr& original_remapping,
    const mojom::ButtonRemappingPtr& new_remapping,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  if (new_remapping->remapping_action &&
      original_remapping->remapping_action != new_remapping->remapping_action) {
    RecordButtonRemappingAction(*(new_remapping->remapping_action),
                                peripheral_kind, "Changed");
  }
}

void RecordInitialButtonRemappingAction(
    const mojom::ButtonRemappingPtr& button_remapping,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  if (!button_remapping->remapping_action) {
    // Add metrics for recording default button remapping.
    const std::string metric_name_prefix = base::StrCat(
        {"ChromeOS.Settings.Device.", ToMetricsString(peripheral_kind),
         ".ButtonRemapping.DefaultRemapping."});
    RecordButtonMetrics(*(button_remapping->button), &metric_name_prefix);
    return;
  }

  // Add metrics for recording remapping actions.
  RecordButtonRemappingAction(*(button_remapping->remapping_action),
                              peripheral_kind, "Initial");
}

std::optional<ui::KeyboardDevice> FindKeyboardWithId(int device_id) {
  const auto& keyboards =
      ui::DeviceDataManager::GetInstance()->GetKeyboardDevices();
  auto iter = base::ranges::find(
      keyboards, device_id,
      [](const ui::KeyboardDevice& keyboard) { return keyboard.id; });
  if (iter == keyboards.end()) {
    return std::nullopt;
  }

  return *iter;
}

std::optional<uint32_t> CountNumberOfDevicesUsedInLast28Days(
    std::string_view pref_name) {
  constexpr base::TimeDelta k28Days = base::Days(28);

  PrefService* pref_service =
      Shell::Get()->session_controller()->GetActivePrefService();
  // Pref service can be null in tests.
  if (!pref_service) {
    return std::nullopt;
  }

  uint32_t num_devices_used = 0;
  const base::Value::Dict& devices_dict = pref_service->GetDict(pref_name);
  for (const auto device_entry : devices_dict) {
    const auto* device = device_entry.second.GetIfDict();
    if (!device) {
      continue;
    }

    const auto* last_updated_value = device->Find(prefs::kLastUpdatedKey);
    if (!last_updated_value) {
      continue;
    }

    const auto last_updated_time = base::ValueToTime(*last_updated_value);
    if (!last_updated_time) {
      continue;
    }

    if (base::Time::Now() - *last_updated_time <= k28Days) {
      num_devices_used++;
    }
  }

  return num_devices_used;
}

void RecordNumberOfMiceUsedInLast28Days() {
  if (auto num_devices = CountNumberOfDevicesUsedInLast28Days(
          prefs::kMouseDeviceSettingsDictPref);
      num_devices) {
    base::UmaHistogramCounts100(
        "ChromeOS.Settings.Device.Mouse.External.NumConnectedLast28Days",
        *num_devices);
  }
}

void RecordNumberOfKeyboardsUsedInLast28Days() {
  if (auto num_devices = CountNumberOfDevicesUsedInLast28Days(
          prefs::kKeyboardDeviceSettingsDictPref);
      num_devices) {
    base::UmaHistogramCounts100(
        "ChromeOS.Settings.Device.Keyboard.External.NumConnectedLast28Days",
        *num_devices);
  }
}

void RecordNumberOfTouchpadsUsedInLast28Days() {
  if (auto num_devices = CountNumberOfDevicesUsedInLast28Days(
          prefs::kTouchpadDeviceSettingsDictPref);
      num_devices) {
    base::UmaHistogramCounts100(
        "ChromeOS.Settings.Device.Touchpad.External.NumConnectedLast28Days",
        *num_devices);
  }
}

void RecordKeyPresenseMetrics(const mojom::Keyboard& keyboard) {
  // Only record for the internal keyboard.
  if (keyboard.is_external) {
    return;
  }

  // For each modifier key, record only what the key is _remapped_ to. So
  // iterate through each modifier key and check if the key is remapped to
  // something else. Then record what the key is remapped to.
  for (const auto& modifier_key : keyboard.modifier_keys) {
    auto remapped_modifier_iter =
        keyboard.settings->modifier_remappings.find(modifier_key);
    const auto remapped_to_key =
        (remapped_modifier_iter != keyboard.settings->modifier_remappings.end())
            ? remapped_modifier_iter->second
            : modifier_key;

    // Skip keys remapped to void since its as though that key does not exist at
    // all.
    if (remapped_to_key == ui::mojom::ModifierKey::kVoid) {
      continue;
    }

    // Record what the key is remapped to and note whether it is via a remapping
    // or the default value for the key.
    base::UmaHistogramEnumeration(
        base::StrCat({"ChromeOS.Inputs.KeyUsage.Internal.",
                      GetModifierKeyName(remapped_to_key)}),
        (modifier_key == remapped_to_key)
            ? ui::KeyUsageCategory::kPhysicallyPresent
            : ui::KeyUsageCategory::kVirtuallyPresent);
  }

  const auto* top_row_action_keys =
      Shell::Get()->keyboard_capability()->GetTopRowActionKeys(keyboard.id);
  if (!top_row_action_keys) {
    return;
  }

  for (const auto& top_row_action_key : *top_row_action_keys) {
    // All top row keys are physically present, no way to virtually remap to
    // them.
    base::UmaHistogramEnumeration(
        base::StrCat({"ChromeOS.Inputs.KeyUsage.Internal.",
                      GetTopRowActionKeyName(top_row_action_key)}),
        ui::KeyUsageCategory::kPhysicallyPresent);
  }
}

void RecordDeviceTypeOfRemappedButton(
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  base::UmaHistogramEnumeration(
      "ChromeOS.Settings.Device.ButtonRemapping.DeviceTypeOfRemappedButton",
      peripheral_kind);
}

}  // namespace

InputDeviceSettingsMetricsManager::InputDeviceSettingsMetricsManager() =
    default;
InputDeviceSettingsMetricsManager::~InputDeviceSettingsMetricsManager() =
    default;

void InputDeviceSettingsMetricsManager::RecordKeyboardInitialMetrics(
    const mojom::Keyboard& keyboard) {
  // Record this metric every time a mouse is plugged/unplugged to account for
  // if a user session lasts for >28 days.
  if (keyboard.is_external) {
    RecordNumberOfKeyboardsUsedInLast28Days();
  }

  // Only record the metrics once for each keyboard.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_keyboards_.find(account_id);

  if (iter != recorded_keyboards_.end() &&
      base::Contains(iter->second, keyboard.device_key)) {
    return;
  }
  recorded_keyboards_[account_id].insert(keyboard.device_key);

  const std::string keyboard_metrics_prefix =
      GetKeyboardMetricsPrefix(keyboard);

  base::UmaHistogramBoolean(keyboard_metrics_prefix + "TopRowAreFKeys.Initial",
                            keyboard.settings->top_row_are_fkeys);
  // Only record BlockMetaFKeyRewrites when keyboard is external/external
  // chromeos.
  if (keyboard.is_external) {
    base::UmaHistogramBoolean(
        keyboard_metrics_prefix + "BlockMetaFKeyRewrites.Initial",
        keyboard.settings->suppress_meta_fkey_rewrites);
  }

  // Record metrics for modifier remappings.
  for (const auto modifier_key : keyboard.modifier_keys) {
    const auto modifier_name = GetModifierKeyName(modifier_key);
    const auto key_remapped_to =
        GetModifierRemappingTo(*keyboard.settings, modifier_key);
    const std::string modifier_remapping_metrics =
        base::StrCat({keyboard_metrics_prefix, "Modifiers.", modifier_name,
                      "RemappedTo.Initial"});
    base::UmaHistogramEnumeration(modifier_remapping_metrics, key_remapped_to);
  }

  // Record remapping metrics when keyboard is initialized.
  if (base::Contains(keyboard.modifier_keys,
                     ui::mojom::ModifierKey::kRightAlt)) {
    RecordSplitModifierRemappingHash(keyboard);
  } else {
    RecordModifierRemappingHash(keyboard);
  }
  RecordKeyboardNumberOfKeysRemapped(keyboard);
  if (ShouldRecordSixPackKeyMetrics(keyboard)) {
    RecordSixPackKeyInfo(keyboard, ui::VKEY_DELETE, /*is_initial_value=*/true);
    RecordSixPackKeyInfo(keyboard, ui::VKEY_INSERT, /*is_initial_value=*/true);
    RecordSixPackKeyInfo(keyboard, ui::VKEY_HOME, /*is_initial_value=*/true);
    RecordSixPackKeyInfo(keyboard, ui::VKEY_END, /*is_initial_value=*/true);
    RecordSixPackKeyInfo(keyboard, ui::VKEY_PRIOR, /*is_initial_value=*/true);
    RecordSixPackKeyInfo(keyboard, ui::VKEY_NEXT, /*is_initial_value=*/true);
  }

  if (ShouldRecordFkeyMetrics(keyboard)) {
    base::UmaHistogramEnumeration(keyboard_metrics_prefix + "F11.Initial",
                                  keyboard.settings->f11.value());
    base::UmaHistogramEnumeration(keyboard_metrics_prefix + "F12.Initial",
                                  keyboard.settings->f12.value());
  }
  RecordKeyPresenseMetrics(keyboard);
}

void InputDeviceSettingsMetricsManager::RecordKeyboardChangedMetrics(
    const mojom::Keyboard& keyboard,
    const mojom::KeyboardSettings& old_settings) {
  const std::string keyboard_metrics_prefix =
      GetKeyboardMetricsPrefix(keyboard);

  if (keyboard.settings->top_row_are_fkeys != old_settings.top_row_are_fkeys) {
    base::UmaHistogramBoolean(
        keyboard_metrics_prefix + "TopRowAreFKeys.Changed",
        keyboard.settings->top_row_are_fkeys);
  }
  // Only record BlockMetaFKeyRewrites when keyboard is external/external
  // chromeos.
  if (keyboard.is_external && keyboard.settings->suppress_meta_fkey_rewrites !=
                                  old_settings.suppress_meta_fkey_rewrites) {
    base::UmaHistogramBoolean(
        keyboard_metrics_prefix + "BlockMetaFKeyRewrites.Changed",
        keyboard.settings->suppress_meta_fkey_rewrites);
  }

  // Record metrics for modifier remappings.
  for (const auto modifier_key : keyboard.modifier_keys) {
    const auto modifier_name = GetModifierKeyName(modifier_key);
    const auto key_remapped_to_before =
        GetModifierRemappingTo(old_settings, modifier_key);
    const auto key_remapped_to =
        GetModifierRemappingTo(*keyboard.settings, modifier_key);
    // Only emit the metric if the modifier remapping is changed.
    if (key_remapped_to_before != key_remapped_to) {
      const std::string modifier_remapping_metrics =
          base::StrCat({keyboard_metrics_prefix, "Modifiers.", modifier_name,
                        "RemappedTo.Changed"});
      base::UmaHistogramEnumeration(modifier_remapping_metrics,
                                    key_remapped_to);
    }
  }

  if (ShouldRecordSixPackKeyMetrics(keyboard)) {
    CHECK(keyboard.settings->six_pack_key_remappings);
    if (keyboard.settings->six_pack_key_remappings->del !=
        old_settings.six_pack_key_remappings->del) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_DELETE,
                           /*is_initial_value=*/false);
    }
    if (keyboard.settings->six_pack_key_remappings->insert !=
        old_settings.six_pack_key_remappings->insert) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_INSERT,
                           /*is_initial_value=*/false);
    }
    if (keyboard.settings->six_pack_key_remappings->home !=
        old_settings.six_pack_key_remappings->home) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_HOME, /*is_initial_value=*/false);
    }
    if (keyboard.settings->six_pack_key_remappings->end !=
        old_settings.six_pack_key_remappings->end) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_END, /*is_initial_value=*/false);
    }
    if (keyboard.settings->six_pack_key_remappings->page_up !=
        old_settings.six_pack_key_remappings->page_up) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_PRIOR,
                           /*is_initial_value=*/false);
    }
    if (keyboard.settings->six_pack_key_remappings->page_down !=
        old_settings.six_pack_key_remappings->page_down) {
      RecordSixPackKeyInfo(keyboard, ui::VKEY_NEXT, /*is_initial_value=*/false);
    }
  }

  if (ShouldRecordFkeyMetrics(keyboard)) {
    if (keyboard.settings->f11 != old_settings.f11) {
      base::UmaHistogramEnumeration(keyboard_metrics_prefix + "F11.Changed",
                                    keyboard.settings->f11.value());
    }
    if (keyboard.settings->f12 != old_settings.f12) {
      base::UmaHistogramEnumeration(keyboard_metrics_prefix + "F12.Changed",
                                    keyboard.settings->f12.value());
    }
  }
  HandleSettingsUpdatedMetric(keyboard);
}

void InputDeviceSettingsMetricsManager::RecordKeyboardNumberOfKeysReset(
    const mojom::Keyboard& keyboard,
    const mojom::KeyboardSettings& default_settings) {
  const int num_keys_reset = GetNumberOfNonDefaultRemappings(
      *keyboard.settings, default_settings.modifier_remappings);

  if (num_keys_reset != 0) {
    const std::string keyboard_metrics = base::StrCat(
        {GetKeyboardMetricsPrefix(keyboard), "Modifiers.NumberOfKeysReset"});
    base::UmaHistogramCounts100(keyboard_metrics, num_keys_reset);
  }
  HandleSettingsUpdatedMetric(keyboard);
}

void InputDeviceSettingsMetricsManager::RecordMouseInitialMetrics(
    const mojom::Mouse& mouse) {
  // Record this metric every time a mouse is plugged/unplugged to account for
  // if a user session lasts for >28 days.
  RecordNumberOfMiceUsedInLast28Days();

  // Only record the metrics once for each mouse.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_mice_.find(account_id);

  if (iter != recorded_mice_.end() &&
      base::Contains(iter->second, mouse.device_key)) {
    return;
  }
  recorded_mice_[account_id].insert(mouse.device_key);

  PointerSensitivity sensitivity =
      static_cast<PointerSensitivity>(mouse.settings->sensitivity);
  PointerSensitivity scroll_sensitivity =
      static_cast<PointerSensitivity>(mouse.settings->scroll_sensitivity);

  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.Mouse.AccelerationEnabled.Initial",
      mouse.settings->acceleration_enabled);
  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.Mouse.ScrollAcceleration.Initial",
      mouse.settings->scroll_acceleration);
  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.Mouse.ReverseScrolling.Initial",
      mouse.settings->reverse_scrolling);
  base::UmaHistogramEnumeration(
      "ChromeOS.Settings.Device.Mouse.Sensitivity.Initial", sensitivity);
  base::UmaHistogramEnumeration(
      "ChromeOS.Settings.Device.Mouse.ScrollSensitivity.Initial",
      scroll_sensitivity);
  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.Mouse.SwapPrimaryButtons.Initial",
      mouse.settings->swap_right);

  bool user_remapped_mouse_button = false;
  for (const auto& button_remapping : mouse.settings->button_remappings) {
    if (button_remapping->remapping_action) {
      user_remapped_mouse_button = true;
    }
    RecordInitialButtonRemappingAction(
        button_remapping, InputDeviceSettingsMetricsManager::
                              PeripheralCustomizationMetricsType::kMouse);
  }

  if (user_remapped_mouse_button) {
    RecordDeviceTypeOfRemappedButton(
        PeripheralCustomizationMetricsType::kMouse);
  }
}

void InputDeviceSettingsMetricsManager::RecordRemappingActionWhenButtonPressed(
    const mojom::RemappingAction& remapping_action,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  // Record grouped button pressed event based on peripheral type.
  base::UmaHistogramEnumeration(
      "ChromeOS.Settings.Device.ButtonRemapping.Pressed", peripheral_kind);

  RecordButtonRemappingAction(remapping_action, peripheral_kind,
                              /*metrics_name_suffix=*/"Pressed");
}

void InputDeviceSettingsMetricsManager::RecordMouseChangedMetrics(
    const mojom::Mouse& mouse,
    const mojom::MouseSettings& old_settings) {
  if (mouse.settings->acceleration_enabled !=
      old_settings.acceleration_enabled) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.Mouse.AccelerationEnabled.Changed",
        mouse.settings->acceleration_enabled);
  }
  if (mouse.settings->scroll_acceleration != old_settings.scroll_acceleration) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.Mouse.ScrollAcceleration.Changed",
        mouse.settings->scroll_acceleration);
  }
  if (mouse.settings->reverse_scrolling != old_settings.reverse_scrolling) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.Mouse.ReverseScrolling.Changed",
        mouse.settings->reverse_scrolling);
  }
  if (mouse.settings->sensitivity != old_settings.sensitivity) {
    PointerSensitivity sensitivity =
        static_cast<PointerSensitivity>(mouse.settings->sensitivity);
    base::UmaHistogramEnumeration(
        "ChromeOS.Settings.Device.Mouse.Sensitivity.Changed", sensitivity);
    const int speed_difference =
        mouse.settings->sensitivity - old_settings.sensitivity;
    const std::string delta_sensitivity_metric =
        speed_difference > 0
            ? "ChromeOS.Settings.Device.Mouse.Sensitivity.Increase"
            : "ChromeOS.Settings.Device.Mouse.Sensitivity.Decrease";
    base::UmaHistogramEnumeration(
        delta_sensitivity_metric,
        static_cast<PointerSensitivity>(abs(speed_difference)));
  }
  if (mouse.settings->scroll_sensitivity != old_settings.scroll_sensitivity) {
    PointerSensitivity scroll_sensitivity =
        static_cast<PointerSensitivity>(mouse.settings->scroll_sensitivity);
    base::UmaHistogramEnumeration(
        "ChromeOS.Settings.Device.Mouse.ScrollSensitivity.Changed",
        scroll_sensitivity);
    const int speed_difference =
        mouse.settings->scroll_sensitivity - old_settings.scroll_sensitivity;
    const std::string delta_scroll_sensitivity_metric =
        speed_difference > 0
            ? "ChromeOS.Settings.Device.Mouse.ScrollSensitivity.Increase"
            : "ChromeOS.Settings.Device.Mouse.ScrollSensitivity.Decrease";
    base::UmaHistogramEnumeration(
        delta_scroll_sensitivity_metric,
        static_cast<PointerSensitivity>(abs(speed_difference)));
  }
  if (mouse.settings->swap_right != old_settings.swap_right) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.Mouse.SwapPrimaryButtons.Changed",
        mouse.settings->swap_right);
  }
  for (const auto& new_remapping : mouse.settings->button_remappings) {
    for (const auto& original_remapping : old_settings.button_remappings) {
      if (original_remapping->button == new_remapping->button) {
        RecordButtonRemappingNameIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kMouse);
        RecordButtonRemappingActionIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kMouse);
      }
    }
  }
  HandleSettingsUpdatedMetric(mouse);
}

void InputDeviceSettingsMetricsManager::RecordPointingStickInitialMetrics(
    const mojom::PointingStick& pointing_stick) {
  // Only record the metrics once for each pointing stick.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_pointing_sticks_.find(account_id);

  if (iter != recorded_pointing_sticks_.end() &&
      base::Contains(iter->second, pointing_stick.device_key)) {
    return;
  }
  recorded_pointing_sticks_[account_id].insert(pointing_stick.device_key);

  PointerSensitivity sensitivity =
      static_cast<PointerSensitivity>(pointing_stick.settings->sensitivity);

  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.PointingStick.AccelerationEnabled.Initial",
      pointing_stick.settings->acceleration_enabled);
  base::UmaHistogramEnumeration(
      "ChromeOS.Settings.Device.PointingStick.Sensitivity.Initial",
      sensitivity);
  base::UmaHistogramBoolean(
      "ChromeOS.Settings.Device.PointingStick.SwapPrimaryButtons.Initial",
      pointing_stick.settings->swap_right);
}

void InputDeviceSettingsMetricsManager::RecordPointingStickChangedMetrics(
    const mojom::PointingStick& pointing_stick,
    const mojom::PointingStickSettings& old_settings) {
  if (pointing_stick.settings->acceleration_enabled !=
      old_settings.acceleration_enabled) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.PointingStick.AccelerationEnabled.Changed",
        pointing_stick.settings->acceleration_enabled);
  }
  if (pointing_stick.settings->sensitivity != old_settings.sensitivity) {
    PointerSensitivity sensitivity =
        static_cast<PointerSensitivity>(pointing_stick.settings->sensitivity);
    base::UmaHistogramEnumeration(
        "ChromeOS.Settings.Device.PointingStick.Sensitivity.Changed",
        sensitivity);
    const int speed_difference =
        pointing_stick.settings->sensitivity - old_settings.sensitivity;
    const std::string delta_sensitivity_metric =
        speed_difference > 0
            ? "ChromeOS.Settings.Device.PointingStick.Sensitivity.Increase"
            : "ChromeOS.Settings.Device.PointingStick.Sensitivity.Decrease";
    base::UmaHistogramEnumeration(
        delta_sensitivity_metric,
        static_cast<PointerSensitivity>(abs(speed_difference)));
  }
  if (pointing_stick.settings->swap_right != old_settings.swap_right) {
    base::UmaHistogramBoolean(
        "ChromeOS.Settings.Device.PointingStick.SwapPrimaryButtons.Changed",
        pointing_stick.settings->swap_right);
  }
  HandleSettingsUpdatedMetric(pointing_stick);
}

void InputDeviceSettingsMetricsManager::RecordTouchpadInitialMetrics(
    const mojom::Touchpad& touchpad) {
  // Record this metric every time a mouse is plugged/unplugged to account for
  // if a user session lasts for >28 days.
  if (touchpad.is_external) {
    RecordNumberOfTouchpadsUsedInLast28Days();
  }

  // Only record the metrics once for each Touchpad.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_touchpads_.find(account_id);

  if (iter != recorded_touchpads_.end() &&
      base::Contains(iter->second, touchpad.device_key)) {
    return;
  }
  recorded_touchpads_[account_id].insert(touchpad.device_key);

  const std::string touchpad_metrics_prefix =
      touchpad.is_external ? "ChromeOS.Settings.Device.Touchpad.External."
                           : "ChromeOS.Settings.Device.Touchpad.Internal.";
  PointerSensitivity sensitivity =
      static_cast<PointerSensitivity>(touchpad.settings->sensitivity);

  base::UmaHistogramBoolean(
      touchpad_metrics_prefix + "AccelerationEnabled.Initial",
      touchpad.settings->acceleration_enabled);
  base::UmaHistogramBoolean(
      touchpad_metrics_prefix + "ReverseScrolling.Initial",
      touchpad.settings->reverse_scrolling);
  base::UmaHistogramEnumeration(touchpad_metrics_prefix + "Sensitivity.Initial",
                                sensitivity);
  base::UmaHistogramBoolean(touchpad_metrics_prefix + "TapDragging.Initial",
                            touchpad.settings->tap_dragging_enabled);
  base::UmaHistogramBoolean(touchpad_metrics_prefix + "TapToClick.Initial",
                            touchpad.settings->tap_to_click_enabled);

  if (touchpad.is_haptic) {
    PointerSensitivity haptic_sensitivity =
        static_cast<PointerSensitivity>(touchpad.settings->haptic_sensitivity);
    base::UmaHistogramBoolean(touchpad_metrics_prefix + "HapticEnabled.Initial",
                              touchpad.settings->haptic_enabled);
    base::UmaHistogramEnumeration(
        touchpad_metrics_prefix + "HapticSensitivity.Initial",
        haptic_sensitivity);
  }

  if (features::IsAltClickAndSixPackCustomizationEnabled()) {
    base::UmaHistogramEnumeration(
        touchpad_metrics_prefix + "SimulateRightClick.Initial",
        touchpad.settings->simulate_right_click);
  }
}

void InputDeviceSettingsMetricsManager::RecordTouchpadChangedMetrics(
    const mojom::Touchpad& touchpad,
    const mojom::TouchpadSettings& old_settings) {
  const std::string touchpad_metrics_prefix =
      touchpad.is_external ? "ChromeOS.Settings.Device.Touchpad.External."
                           : "ChromeOS.Settings.Device.Touchpad.Internal.";
  if (touchpad.settings->acceleration_enabled !=
      old_settings.acceleration_enabled) {
    base::UmaHistogramBoolean(
        touchpad_metrics_prefix + "AccelerationEnabled.Changed",
        touchpad.settings->acceleration_enabled);
  }
  if (touchpad.settings->reverse_scrolling != old_settings.reverse_scrolling) {
    base::UmaHistogramBoolean(
        touchpad_metrics_prefix + "ReverseScrolling.Changed",
        touchpad.settings->reverse_scrolling);
  }
  if (touchpad.settings->sensitivity != old_settings.sensitivity) {
    PointerSensitivity sensitivity =
        static_cast<PointerSensitivity>(touchpad.settings->sensitivity);
    base::UmaHistogramEnumeration(
        touchpad_metrics_prefix + "Sensitivity.Changed", sensitivity);
    const int speed_difference =
        touchpad.settings->sensitivity - old_settings.sensitivity;
    const std::string delta_metric_suffix =
        speed_difference > 0 ? "Sensitivity.Increase" : "Sensitivity.Decrease";
    base::UmaHistogramEnumeration(
        base::StrCat({touchpad_metrics_prefix, delta_metric_suffix}),
        static_cast<PointerSensitivity>(abs(speed_difference)));
  }
  if (touchpad.settings->tap_dragging_enabled !=
      old_settings.tap_dragging_enabled) {
    base::UmaHistogramBoolean(touchpad_metrics_prefix + "TapDragging.Changed",
                              touchpad.settings->tap_dragging_enabled);
  }
  if (touchpad.settings->tap_to_click_enabled !=
      old_settings.tap_to_click_enabled) {
    base::UmaHistogramBoolean(touchpad_metrics_prefix + "TapToClick.Changed",
                              touchpad.settings->tap_to_click_enabled);
  }
  if (touchpad.is_haptic) {
    if (touchpad.settings->haptic_enabled != old_settings.haptic_enabled) {
      bool haptic_enabled = touchpad.settings->haptic_enabled;
      base::UmaHistogramBoolean(
          touchpad_metrics_prefix + "HapticEnabled.Changed", haptic_enabled);
    }
    if (touchpad.settings->haptic_sensitivity !=
        old_settings.haptic_sensitivity) {
      PointerSensitivity haptic_sensitivity = static_cast<PointerSensitivity>(
          touchpad.settings->haptic_sensitivity);
      base::UmaHistogramEnumeration(
          touchpad_metrics_prefix + "HapticSensitivity.Changed",
          haptic_sensitivity);
      const int speed_difference = touchpad.settings->haptic_sensitivity -
                                   old_settings.haptic_sensitivity;
      const std::string delta_metric_suffix =
          speed_difference > 0 ? "HapticSensitivity.Increase"
                               : "HapticSensitivity.Decrease";
      base::UmaHistogramEnumeration(
          base::StrCat({touchpad_metrics_prefix, delta_metric_suffix}),
          static_cast<PointerSensitivity>(abs(speed_difference)));
    }
  }
  if (features::IsAltClickAndSixPackCustomizationEnabled() &&
      touchpad.settings->simulate_right_click !=
          old_settings.simulate_right_click) {
    base::UmaHistogramEnumeration(
        touchpad_metrics_prefix + "SimulateRightClick.Changed",
        touchpad.settings->simulate_right_click);
  }
  HandleSettingsUpdatedMetric(touchpad);
}

void InputDeviceSettingsMetricsManager::RecordGraphicsTabletInitialMetrics(
    const mojom::GraphicsTablet& graphics_tablet) {
  // Only record the metrics once for each graphics tablet.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_graphics_tablets_.find(account_id);

  if (iter != recorded_graphics_tablets_.end() &&
      base::Contains(iter->second, graphics_tablet.device_key)) {
    return;
  }
  recorded_graphics_tablets_[account_id].insert(graphics_tablet.device_key);

  bool user_remapped_pen_button = false;
  for (const auto& button_remapping :
       graphics_tablet.settings->pen_button_remappings) {
    if (button_remapping->remapping_action) {
      user_remapped_pen_button = true;
    }
    RecordInitialButtonRemappingAction(
        button_remapping,
        InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
            kGraphicsTabletPen);
  }

  if (user_remapped_pen_button) {
    RecordDeviceTypeOfRemappedButton(
        PeripheralCustomizationMetricsType::kGraphicsTabletPen);
  }

  bool user_remapped_tablet_button = false;
  for (const auto& button_remapping :
       graphics_tablet.settings->tablet_button_remappings) {
    if (button_remapping->remapping_action) {
      user_remapped_tablet_button = true;
    }
    RecordInitialButtonRemappingAction(
        button_remapping,
        InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
            kGraphicsTablet);
  }

  if (user_remapped_tablet_button) {
    RecordDeviceTypeOfRemappedButton(
        PeripheralCustomizationMetricsType::kGraphicsTablet);
  }
}

void InputDeviceSettingsMetricsManager::RecordGraphicsTabletChangedMetrics(
    const mojom::GraphicsTablet& graphics_tablet,
    const mojom::GraphicsTabletSettings& old_settings) {
  for (const auto& new_remapping :
       graphics_tablet.settings->pen_button_remappings) {
    for (const auto& original_remapping : old_settings.pen_button_remappings) {
      if (original_remapping->button == new_remapping->button) {
        RecordButtonRemappingNameIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kGraphicsTabletPen);
        RecordButtonRemappingActionIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kGraphicsTabletPen);
      }
    }
  }
  for (const auto& new_remapping :
       graphics_tablet.settings->tablet_button_remappings) {
    for (const auto& original_remapping :
         old_settings.tablet_button_remappings) {
      if (original_remapping->button == new_remapping->button) {
        RecordButtonRemappingNameIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kGraphicsTablet);
        RecordButtonRemappingActionIfChanged(
            original_remapping, new_remapping,
            InputDeviceSettingsMetricsManager::
                PeripheralCustomizationMetricsType::kGraphicsTablet);
      }
    }
  }
}

void InputDeviceSettingsMetricsManager::RecordModifierRemappingHash(
    const mojom::Keyboard& keyboard) {
  // Compute hash by left-shifting by `kModifierHashWidth` and then inserting
  // the modifier value from prefs at into the lowest `kModifierHashWidth` bits.
  uint32_t hash = 0;
  for (ssize_t i = kNumModifiers - 1u; i >= 0; i--) {
    const auto modifier_key = kModifierNames[i].modifier_key;
    auto iter = keyboard.settings->modifier_remappings.find(modifier_key);
    const auto remapped_key_value =
        iter == keyboard.settings->modifier_remappings.end()
            ? static_cast<uint32_t>(modifier_key)
            : static_cast<uint32_t>(iter->second);

    // Check that shifting and adding value will not overflow `hash`.
    DCHECK(remapped_key_value <= kMaxModifierValue && remapped_key_value >= 0);
    DCHECK(hash < (1u << ((sizeof(uint32_t) * 8u) - kModifierHashWidth)));

    hash <<= kModifierHashWidth;
    hash += static_cast<uint32_t>(remapped_key_value);
  }

  // If the computed hash matches the hash when settings are in a default state,
  // the metric should not be published.
  if (hash != kDefaultModifierHash) {
    const std::string metrics =
        base::StrCat({GetKeyboardMetricsPrefix(keyboard), "Modifiers.Hash"});
    base::UmaHistogramSparse(metrics, static_cast<int>(hash));
  }
}

void InputDeviceSettingsMetricsManager::RecordSplitModifierRemappingHash(
    const mojom::Keyboard& keyboard) {
  // Compute hash by left-shifting by `kModifierHashWidth` and then inserting
  // the modifier value from prefs at into the lowest `kModifierHashWidth` bits.
  uint32_t hash = 0;
  for (ssize_t i = kSplitModifierNumModifiers - 1u; i >= 0; i--) {
    const auto modifier_key = kModifierNames[i].modifier_key;
    auto iter = keyboard.settings->modifier_remappings.find(modifier_key);
    const auto remapped_key_value =
        iter == keyboard.settings->modifier_remappings.end()
            ? static_cast<uint32_t>(modifier_key)
            : static_cast<uint32_t>(iter->second);

    // Check that shifting and adding value will not overflow `hash`.
    DCHECK(remapped_key_value <= kMaxModifierValue && remapped_key_value >= 0);
    DCHECK(hash < (1u << ((sizeof(uint32_t) * 8u) - kModifierHashWidth)));

    hash <<= kModifierHashWidth;
    hash += static_cast<uint32_t>(remapped_key_value);
  }

  // If the computed hash matches the hash when settings are in a default state,
  // the metric should not be published.
  if (hash != kSplitModifierDefaultModifierHash) {
    const std::string metrics =
        "ChromeOS.Settings.Device.Keyboard.InternalSplitModifier.Modifiers."
        "Hash";
    base::UmaHistogramSparse(metrics, static_cast<int>(hash));
  }
}

void InputDeviceSettingsMetricsManager::RecordSixPackKeyInfo(
    const mojom::Keyboard& keyboard,
    ui::KeyboardCode key_code,
    bool is_initial_value) {
  base::UmaHistogramEnumeration(
      GetSixPackKeyMetricName(GetKeyboardMetricsPrefix(keyboard), key_code,
                              is_initial_value),
      GetSixPackKeyModifier(keyboard, key_code));
}

void InputDeviceSettingsMetricsManager::RecordKeyboardMouseComboDeviceMetric(
    const mojom::Keyboard& keyboard,
    const mojom::Mouse& mouse) {
  static base::NoDestructor<base::flat_set<std::string>> logged_devices;

  auto [_, inserted] = logged_devices->insert(keyboard.device_key);
  if (!inserted) {
    return;
  }

  auto keyboard_device = FindKeyboardWithId(keyboard.id);
  if (!keyboard_device) {
    return;
  }

  if (GetDeviceType(*keyboard_device) == DeviceType::kKeyboardMouseCombo) {
    base::UmaHistogramEnumeration(
        "ChromeOS.Inputs.ComboDeviceClassification",
        ui::ComboDeviceClassification::kKnownComboDevice);
  } else {
    LOG(WARNING) << base::StringPrintf(
        "Classification for combo device '%s' with identifier '%s' is "
        "unknown.",
        keyboard.name.c_str(), keyboard.device_key.c_str());
    base::UmaHistogramEnumeration("ChromeOS.Inputs.ComboDeviceClassification",
                                  ui::ComboDeviceClassification::kUnknown);
  }
}

void InputDeviceSettingsMetricsManager::RecordNewButtonRegisteredMetrics(
    const mojom::Button& button,
    InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
        peripheral_kind) {
  const std::string metric_name_prefix = base::StrCat(
      {"ChromeOS.Settings.Device.", ToMetricsString(peripheral_kind),
       ".ButtonRemapping.Registered."});
  RecordButtonMetrics(button, &metric_name_prefix);
}

void InputDeviceSettingsMetricsManager::RecordCompanionAppAvailable(
    const std::string& device_key) {
  // Only record the metrics once per device.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_companion_app_available_device_keys_.find(account_id);
  if (iter != recorded_companion_app_available_device_keys_.end() &&
      base::Contains(iter->second, device_key)) {
    return;
  }

  recorded_companion_app_available_device_keys_[account_id].insert(device_key);
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceCompanionAppState",
      InputDeviceSettingsMetricsManager::CompanionAppState::kAvailable);
}

void InputDeviceSettingsMetricsManager::RecordCompanionAppInstalled(
    const std::string& device_key) {
  // Only record the metrics once per device.
  const auto account_id =
      Shell::Get()->session_controller()->GetActiveAccountId();
  auto iter = recorded_companion_app_installed_device_keys_.find(account_id);
  if (iter != recorded_companion_app_installed_device_keys_.end() &&
      base::Contains(iter->second, device_key)) {
    return;
  }

  recorded_companion_app_installed_device_keys_[account_id].insert(device_key);
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceCompanionAppState",
      InputDeviceSettingsMetricsManager::CompanionAppState::kInstalled);
}

}  // namespace ash