chromium/ash/system/input_device_settings/pref_handlers/mouse_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/mouse_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-forward.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_logging.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/input_device_tracker.h"
#include "ash/system/input_device_settings/settings_updated_metrics_info.h"
#include "base/check.h"
#include "base/json/values_util.h"
#include "base/strings/strcat.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"

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.
struct ForceMouseSettingPersistence {
  bool swap_right = false;
  bool sensitivity = false;
  bool reverse_scrolling = false;
  bool acceleration_enabled = false;
  bool scroll_acceleration = false;
  bool scroll_sensitivity = false;
};

bool GetDefaultSwapRightValue(const mojom::MousePolicies& mouse_policies) {
  if (mouse_policies.swap_right_policy &&
      mouse_policies.swap_right_policy->policy_status ==
          mojom::PolicyStatus::kRecommended) {
    return mouse_policies.swap_right_policy->value;
  }

  return kDefaultSwapRight;
}

// Append all missing button remappings to the current list with default
// remapping action.
void UpdateButtonRemappingsWithCompleteList(
    mojom::MouseButtonConfig mouse_button_config,
    std::vector<mojom::ButtonRemappingPtr>& current_button_remappings) {
  auto default_remappings =
      GetButtonRemappingListForConfig(mouse_button_config);
  for (auto& remapping : default_remappings) {
    const auto iter = base::ranges::find(
        current_button_remappings, *remapping->button,
        [](const mojom::ButtonRemappingPtr& current_remapping) {
          return *current_remapping->button;
        });
    if (iter == current_button_remappings.end()) {
      current_button_remappings.push_back(std::move(remapping));
    }
  }
}

// GetMouseSettingsFromPrefs returns a mouse settings based on user prefs
// to be used as settings for new mouses.
mojom::MouseSettingsPtr GetMouseSettingsFromPrefs(
    PrefService* prefs,
    const mojom::MousePolicies& mouse_policies,
    ForceMouseSettingPersistence& force_persistence) {
  mojom::MouseSettingsPtr settings = mojom::MouseSettings::New();

  const auto* swap_right_preference =
      prefs->GetUserPrefValue(prefs::kPrimaryMouseButtonRight);
  settings->swap_right = swap_right_preference
                             ? swap_right_preference->GetBool()
                             : GetDefaultSwapRightValue(mouse_policies);
  force_persistence.swap_right = swap_right_preference != nullptr;

  const auto* sensitivity_preference =
      prefs->GetUserPrefValue(prefs::kMouseSensitivity);
  settings->sensitivity = sensitivity_preference
                              ? sensitivity_preference->GetInt()
                              : kDefaultSensitivity;
  force_persistence.sensitivity = sensitivity_preference != nullptr;

  const auto* reverse_scrolling_preference =
      prefs->GetUserPrefValue(prefs::kMouseReverseScroll);
  settings->reverse_scrolling = reverse_scrolling_preference
                                    ? reverse_scrolling_preference->GetBool()
                                    : kDefaultReverseScrolling;
  force_persistence.reverse_scrolling = reverse_scrolling_preference != nullptr;

  const auto* acceleration_enabled_preference =
      prefs->GetUserPrefValue(prefs::kMouseAcceleration);
  settings->acceleration_enabled =
      acceleration_enabled_preference
          ? acceleration_enabled_preference->GetBool()
          : kDefaultAccelerationEnabled;
  force_persistence.acceleration_enabled =
      acceleration_enabled_preference != nullptr;

  const auto* scroll_acceleration_preference =
      prefs->GetUserPrefValue(prefs::kMouseScrollAcceleration);
  settings->scroll_acceleration =
      scroll_acceleration_preference ? scroll_acceleration_preference->GetBool()
                                     : kDefaultScrollAccelerationEnabled;
  force_persistence.scroll_acceleration =
      scroll_acceleration_preference != nullptr;

  const auto* scroll_sensitivity_preference =
      prefs->GetUserPrefValue(prefs::kMouseScrollSensitivity);
  settings->scroll_sensitivity = scroll_sensitivity_preference
                                     ? scroll_sensitivity_preference->GetInt()
                                     : kDefaultScrollSensitivity;
  force_persistence.scroll_sensitivity =
      scroll_sensitivity_preference != nullptr;

  return settings;
}

mojom::MouseSettingsPtr RetrieveMouseSettings(
    const mojom::MousePolicies& mouse_policies,
    const base::Value::Dict& settings_dict) {
  mojom::MouseSettingsPtr settings = mojom::MouseSettings::New();
  settings->swap_right =
      settings_dict.FindBool(prefs::kMouseSettingSwapRight)
          .value_or(GetDefaultSwapRightValue(mouse_policies));
  settings->sensitivity = settings_dict.FindInt(prefs::kMouseSettingSensitivity)
                              .value_or(kDefaultSensitivity);
  settings->reverse_scrolling =
      settings_dict.FindBool(prefs::kMouseSettingReverseScrolling)
          .value_or(kDefaultReverseScrolling);
  settings->acceleration_enabled =
      settings_dict.FindBool(prefs::kMouseSettingAccelerationEnabled)
          .value_or(kDefaultAccelerationEnabled);
  settings->scroll_sensitivity =
      settings_dict.FindInt(prefs::kMouseSettingScrollSensitivity)
          .value_or(kDefaultScrollSensitivity);
  settings->scroll_acceleration =
      settings_dict.FindBool(prefs::kMouseSettingScrollAcceleration)
          .value_or(kDefaultScrollAccelerationEnabled);
  return settings;
}

mojom::MouseSettingsPtr GetDefaultMouseSettings(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies) {
  if (pref_service) {
    return RetrieveMouseSettings(
        mouse_policies, pref_service->GetDict(prefs::kMouseDefaultSettings));
  }

  return RetrieveMouseSettings(mouse_policies, /*settings_dict=*/{});
}

base::Value::Dict ConvertSettingsToDict(
    const mojom::Mouse& mouse,
    const mojom::MousePolicies& mouse_policies,
    const ForceMouseSettingPersistence& force_persistence,
    const base::Value::Dict* existing_settings_dict) {
  // Populate `settings_dict` with all settings in `settings`.
  base::Value::Dict settings_dict;

  if (ShouldPersistSetting(
          mouse_policies.swap_right_policy, prefs::kMouseSettingSwapRight,
          mouse.settings->swap_right, GetDefaultSwapRightValue(mouse_policies),
          force_persistence.swap_right, existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingSwapRight,
                      mouse.settings->swap_right);
  }

  if (ShouldPersistSetting(prefs::kMouseSettingSensitivity,
                           static_cast<int>(mouse.settings->sensitivity),
                           kDefaultSensitivity, force_persistence.sensitivity,
                           existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingSensitivity,
                      mouse.settings->sensitivity);
  }

  if (ShouldPersistSetting(
          prefs::kMouseSettingReverseScrolling,
          mouse.settings->reverse_scrolling, kDefaultReverseScrolling,
          force_persistence.reverse_scrolling, existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingReverseScrolling,
                      mouse.settings->reverse_scrolling);
  }

  if (ShouldPersistSetting(
          prefs::kMouseSettingAccelerationEnabled,
          mouse.settings->acceleration_enabled, kDefaultAccelerationEnabled,
          force_persistence.acceleration_enabled, existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingAccelerationEnabled,
                      mouse.settings->acceleration_enabled);
  }

  if (ShouldPersistSetting(prefs::kMouseSettingScrollSensitivity,
                           static_cast<int>(mouse.settings->scroll_sensitivity),
                           kDefaultScrollSensitivity,
                           force_persistence.scroll_sensitivity,
                           existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingScrollSensitivity,
                      mouse.settings->scroll_sensitivity);
  }

  if (ShouldPersistSetting(prefs::kMouseSettingScrollAcceleration,
                           mouse.settings->scroll_acceleration,
                           kDefaultScrollAccelerationEnabled,
                           force_persistence.scroll_acceleration,
                           existing_settings_dict)) {
    settings_dict.Set(prefs::kMouseSettingScrollAcceleration,
                      mouse.settings->scroll_acceleration);
  }

  return settings_dict;
}

void UpdateButtonRemappingDictPref(PrefService* pref_service,
                                   const mojom::Mouse& mouse,
                                   const base::Time time_stamp) {
  const mojom::MouseSettings& settings = *mouse.settings;
  base::Value::List button_remappings = ConvertButtonRemappingArrayToList(
      settings.button_remappings, mouse.customization_restriction);
  base::Value::Dict button_remappings_dict =
      pref_service->GetDict(prefs::kMouseButtonRemappingsDictPref).Clone();
  button_remappings_dict.Set(mouse.device_key, std::move(button_remappings));
  const auto time_stamp_path =
      base::StrCat({prefs::kLastUpdatedKey, ".", mouse.device_key});
  button_remappings_dict.SetByDottedPath(time_stamp_path,
                                         base::TimeToValue(time_stamp));
  pref_service->SetDict(std::string(prefs::kMouseButtonRemappingsDictPref),
                        std::move(button_remappings_dict));
}

void UpdateMouseSettingsImpl(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies,
    const mojom::Mouse& mouse,
    const ForceMouseSettingPersistence& force_persistence) {
  DCHECK(mouse.settings);
  const base::Time time_stamp = base::Time::Now();
  base::Value::Dict devices_dict =
      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref).Clone();
  base::Value::Dict* existing_settings_dict =
      devices_dict.FindDict(mouse.device_key);

  base::Value::Dict settings_dict = ConvertSettingsToDict(
      mouse, mouse_policies, force_persistence, existing_settings_dict);

  settings_dict.Set(prefs::kLastUpdatedKey, base::TimeToValue(time_stamp));
  // If an old settings dict already exists for the device, merge the updated
  // settings into the old settings. Otherwise, insert the dict at
  // `mouse.device_key`.
  if (existing_settings_dict) {
    existing_settings_dict->Merge(std::move(settings_dict));
  } else {
    devices_dict.Set(mouse.device_key, std::move(settings_dict));
  }

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

  if (features::IsPeripheralCustomizationEnabled()) {
    UpdateButtonRemappingDictPref(pref_service, mouse, time_stamp);
  }
}

mojom::MouseSettingsPtr GetMouseSettingsFromOldLocalStatePrefs(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::MousePolicies& mouse_policies,
    const mojom::Mouse& mouse) {
  mojom::MouseSettingsPtr settings =
      GetDefaultMouseSettings(/*pref_service=*/nullptr, mouse_policies);
  settings->swap_right =
      user_manager::KnownUser(local_state)
          .FindBoolPath(account_id, prefs::kOwnerPrimaryMouseButtonRight)
          .value_or(kDefaultSwapRight);

  return settings;
}

bool HasDefaultSettings(PrefService* pref_service) {
  const auto* pref = pref_service->FindPreference(prefs::kMouseDefaultSettings);
  return pref && pref->HasUserSetting();
}

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

  const auto& settings_metric_info =
      pref_service->GetDict(prefs::kMouseUpdateSettingsMetricInfo);
  const auto* device_metric_info = settings_metric_info.Find(mouse.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(mouse.device_key, metrics_info.ToDict());

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

void InitializeMouseSettingsImpl(PrefService* pref_service,
                                 const mojom::MousePolicies& mouse_policies,
                                 mojom::Mouse* mouse,
                                 bool force_initialize_to_default_settings) {
  if (!pref_service) {
    mouse->settings = GetDefaultMouseSettings(pref_service, mouse_policies);
    return;
  }

  const auto& devices_dict =
      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref);
  const auto* settings_dict = devices_dict.FindDict(mouse->device_key);
  if (force_initialize_to_default_settings) {
    settings_dict = nullptr;
  }

  ForceMouseSettingPersistence force_persistence;
  SettingsUpdatedMetricsInfo::Category category;
  if (settings_dict) {
    category = SettingsUpdatedMetricsInfo::Category::kSynced;
    mouse->settings = RetrieveMouseSettings(mouse_policies, *settings_dict);
  } else if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
                 InputDeviceTracker::InputDeviceCategory::kMouse,
                 mouse->device_key)) {
    category = SettingsUpdatedMetricsInfo::Category::kDefault;
    mouse->settings = GetMouseSettingsFromPrefs(pref_service, mouse_policies,
                                                force_persistence);
  } else {
    mouse->settings = GetDefaultMouseSettings(pref_service, mouse_policies);
    category = HasDefaultSettings(pref_service)
                   ? SettingsUpdatedMetricsInfo::Category::kDefault
                   : SettingsUpdatedMetricsInfo::Category::kFirstEver;
  }
  if (category == SettingsUpdatedMetricsInfo::Category::kSynced) {
    PR_LOG(INFO, Feature::IDS)
        << GetMouseSettingsLog("Synced with latest settings", *mouse);
  } else if (category == SettingsUpdatedMetricsInfo::Category::kDefault) {
    PR_LOG(INFO, Feature::IDS)
        << GetMouseSettingsLog("Using default settings", *mouse);
  } else {
    PR_LOG(INFO, Feature::IDS)
        << GetMouseSettingsLog("First time connected", *mouse);
  }
  if (features::IsPeripheralCustomizationEnabled()) {
    const auto& button_remappings_dict =
        pref_service->GetDict(prefs::kMouseButtonRemappingsDictPref);
    const auto* button_remappings_list =
        button_remappings_dict.FindList(mouse->device_key);
    if (button_remappings_list) {
      auto button_remappings = ConvertListToButtonRemappingArray(
          *button_remappings_list, mouse->customization_restriction);
      UpdateButtonRemappingsWithCompleteList(mouse->mouse_button_config,
                                             button_remappings);
      mouse->settings->button_remappings = std::move(button_remappings);
    } else {
      mouse->settings->button_remappings =
          GetButtonRemappingListForConfig(mouse->mouse_button_config);
    }
  }
  DCHECK(mouse->settings);
  InitializeSettingsUpdateMetricInfo(pref_service, *mouse, category);

  UpdateMouseSettingsImpl(pref_service, mouse_policies, *mouse,
                          force_persistence);

  if (mouse_policies.swap_right_policy &&
      mouse_policies.swap_right_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    mouse->settings->swap_right = mouse_policies.swap_right_policy->value;
  }
}

}  // namespace

MousePrefHandlerImpl::MousePrefHandlerImpl() = default;
MousePrefHandlerImpl::~MousePrefHandlerImpl() = default;

void MousePrefHandlerImpl::InitializeMouseSettings(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies,
    mojom::Mouse* mouse) {
  InitializeMouseSettingsImpl(pref_service, mouse_policies, mouse,
                              /*force_initialize_to_default_settings=*/false);
}

void MousePrefHandlerImpl::UpdateMouseSettings(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies,
    const mojom::Mouse& mouse) {
  UpdateMouseSettingsImpl(pref_service, mouse_policies, mouse,
                          /*force_persistence=*/{});
}

void MousePrefHandlerImpl::InitializeLoginScreenMouseSettings(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::MousePolicies& mouse_policies,
    mojom::Mouse* mouse) {
  // Verify if the flag is enabled.
  if (!features::IsInputDeviceSettingsSplitEnabled()) {
    return;
  }
  CHECK(local_state);

  const auto* settings_dict = GetLoginScreenSettingsDict(
      local_state, account_id,
      mouse->is_external ? prefs::kMouseLoginScreenExternalSettingsPref
                         : prefs::kMouseLoginScreenInternalSettingsPref);
  if (settings_dict) {
    mouse->settings = RetrieveMouseSettings(mouse_policies, *settings_dict);
  } else {
    mouse->settings = GetMouseSettingsFromOldLocalStatePrefs(
        local_state, account_id, mouse_policies, *mouse);
  }

  if (mouse_policies.swap_right_policy &&
      mouse_policies.swap_right_policy->policy_status ==
          mojom::PolicyStatus::kManaged) {
    mouse->settings->swap_right = mouse_policies.swap_right_policy->value;
  }

  if (features::IsPeripheralCustomizationEnabled()) {
    const auto* button_remappings_list = GetLoginScreenButtonRemappingList(
        local_state, account_id,
        prefs::kMouseLoginScreenButtonRemappingListPref);
    if (button_remappings_list) {
      mouse->settings->button_remappings = ConvertListToButtonRemappingArray(
          *button_remappings_list, mouse->customization_restriction);
    } else {
      mouse->settings->button_remappings =
          GetButtonRemappingListForConfig(mouse->mouse_button_config);
    }
  }
}

void MousePrefHandlerImpl::UpdateLoginScreenMouseSettings(
    PrefService* local_state,
    const AccountId& account_id,
    const mojom::MousePolicies& mouse_policies,
    const mojom::Mouse& mouse) {
  CHECK(local_state);
  const auto* pref_name = mouse.is_external
                              ? prefs::kMouseLoginScreenExternalSettingsPref
                              : prefs::kMouseLoginScreenInternalSettingsPref;
  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(
              mouse, mouse_policies, /*force_persistence=*/{}, settings_dict)));

  if (features::IsPeripheralCustomizationEnabled()) {
    const auto* button_remapping_list_pref =
        prefs::kMouseLoginScreenButtonRemappingListPref;
    user_manager::KnownUser(local_state)
        .SetPath(
            account_id, button_remapping_list_pref,
            std::make_optional<base::Value>(ConvertButtonRemappingArrayToList(
                mouse.settings->button_remappings,
                mouse.customization_restriction,
                /*redact_button_names=*/true)));
  }
}

void MousePrefHandlerImpl::InitializeWithDefaultMouseSettings(
    const mojom::MousePolicies& mouse_policies,
    mojom::Mouse* mouse) {
  mouse->settings =
      GetDefaultMouseSettings(/*pref_service=*/nullptr, mouse_policies);
}

void MousePrefHandlerImpl::UpdateDefaultMouseSettings(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies,
    const mojom::Mouse& mouse) {
  // All settings should be persisted fully when storing defaults.
  auto settings_dict =
      ConvertSettingsToDict(mouse, mouse_policies, /*force_persistence=*/{true},
                            /*existing_settings_dict=*/nullptr);
  pref_service->SetDict(prefs::kMouseDefaultSettings, std::move(settings_dict));
}

void MousePrefHandlerImpl::ForceInitializeWithDefaultSettings(
    PrefService* pref_service,
    const mojom::MousePolicies& mouse_policies,
    mojom::Mouse* mouse) {
  InitializeMouseSettingsImpl(pref_service, mouse_policies, mouse,
                              /*force_initialize_to_default_settings=*/true);
}

}  // namespace ash