chromium/ash/system/input_device_settings/input_device_settings_controller_impl.h

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

#ifndef ASH_SYSTEM_INPUT_DEVICE_SETTINGS_INPUT_DEVICE_SETTINGS_CONTROLLER_IMPL_H_
#define ASH_SYSTEM_INPUT_DEVICE_SETTINGS_INPUT_DEVICE_SETTINGS_CONTROLLER_IMPL_H_

#include <memory>

#include "ash/ash_export.h"
#include "ash/login/ui/login_data_dispatcher.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/peripherals_app_delegate.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/system/input_device_settings/device_image.h"
#include "ash/system/input_device_settings/input_device_duplicate_id_finder.h"
#include "ash/system/input_device_settings/input_device_notifier.h"
#include "ash/system/input_device_settings/input_device_settings_metadata_manager.h"
#include "ash/system/input_device_settings/input_device_settings_metrics_manager.h"
#include "ash/system/input_device_settings/input_device_settings_notification_controller.h"
#include "ash/system/input_device_settings/input_device_settings_policy_handler.h"
#include "ash/system/input_device_settings/modifier_split_bypass_checker.h"
#include "ash/system/input_device_settings/pref_handlers/graphics_tablet_pref_handler.h"
#include "ash/system/input_device_settings/pref_handlers/keyboard_pref_handler.h"
#include "ash/system/input_device_settings/pref_handlers/mouse_pref_handler.h"
#include "ash/system/input_device_settings/pref_handlers/pointing_stick_pref_handler.h"
#include "ash/system/input_device_settings/pref_handlers/touchpad_pref_handler.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/message_center/message_center_observer.h"

class AccountId;
class PrefChangeRegistrar;
class PrefRegistrySimple;

namespace ash {

// Controller to manage input device settings.
class ASH_EXPORT InputDeviceSettingsControllerImpl
    : public InputDeviceSettingsController,
      public input_method::InputMethodManager::Observer,
      public SessionObserver,
      public device::BluetoothAdapter::Observer,
      public LoginDataDispatcher::Observer,
      public apps::AppRegistryCache::Observer,
      public message_center::MessageCenterObserver {
 public:
  explicit InputDeviceSettingsControllerImpl(PrefService* local_state);
  InputDeviceSettingsControllerImpl(
      PrefService* local_state,
      std::unique_ptr<KeyboardPrefHandler> keyboard_pref_handler,
      std::unique_ptr<TouchpadPrefHandler> touchpad_pref_handler,
      std::unique_ptr<MousePrefHandler> mouse_pref_handler,
      std::unique_ptr<PointingStickPrefHandler> pointing_stick_pref_handler,
      std::unique_ptr<GraphicsTabletPrefHandler> graphics_tablet_pref_handler,
      scoped_refptr<base::SequencedTaskRunner> task_runner);
  InputDeviceSettingsControllerImpl(const InputDeviceSettingsControllerImpl&) =
      delete;
  InputDeviceSettingsControllerImpl& operator=(
      const InputDeviceSettingsControllerImpl&) = delete;
  ~InputDeviceSettingsControllerImpl() override;

  static void RegisterProfilePrefs(PrefRegistrySimple* pref_registry);

  // Refreshes keyboard info and settings. To be used when the feature is first
  // forcibly enabled.
  void ForceKeyboardSettingRefreshWhenFeatureEnabled();

  // InputDeviceSettingsController:
  std::vector<mojom::KeyboardPtr> GetConnectedKeyboards() override;
  std::vector<mojom::TouchpadPtr> GetConnectedTouchpads() override;
  std::vector<mojom::MousePtr> GetConnectedMice() override;
  std::vector<mojom::PointingStickPtr> GetConnectedPointingSticks() override;
  std::vector<mojom::GraphicsTabletPtr> GetConnectedGraphicsTablets() override;
  const mojom::KeyboardSettings* GetKeyboardSettings(DeviceId id) override;
  const mojom::MouseSettings* GetMouseSettings(DeviceId id) override;
  const mojom::TouchpadSettings* GetTouchpadSettings(DeviceId id) override;
  const mojom::PointingStickSettings* GetPointingStickSettings(
      DeviceId id) override;
  const mojom::GraphicsTabletSettings* GetGraphicsTabletSettings(
      DeviceId id) override;
  const mojom::Keyboard* GetKeyboard(DeviceId id) override;
  const mojom::Mouse* GetMouse(DeviceId id) override;
  const mojom::Touchpad* GetTouchpad(DeviceId id) override;
  const mojom::PointingStick* GetPointingStick(DeviceId id) override;
  const mojom::GraphicsTablet* GetGraphicsTablet(DeviceId id) override;
  const mojom::KeyboardPolicies& GetKeyboardPolicies() override;
  const mojom::MousePolicies& GetMousePolicies() override;
  bool SetKeyboardSettings(DeviceId id,
                           mojom::KeyboardSettingsPtr settings) override;
  bool SetTouchpadSettings(DeviceId id,
                           mojom::TouchpadSettingsPtr settings) override;
  bool SetMouseSettings(DeviceId id, mojom::MouseSettingsPtr settings) override;
  bool SetPointingStickSettings(
      DeviceId id,
      mojom::PointingStickSettingsPtr settings) override;
  bool SetGraphicsTabletSettings(
      DeviceId id,
      mojom::GraphicsTabletSettingsPtr settings) override;
  void OnLoginScreenFocusedPodChanged(const AccountId& account_id) override;
  void StartObservingButtons(DeviceId id) override;
  void StopObservingButtons() override;
  void OnMouseButtonPressed(DeviceId device_id,
                            const mojom::Button& button) override;
  void OnGraphicsTabletButtonPressed(DeviceId device_id,
                                     const mojom::Button& button) override;
  void GetDeviceImageDataUrl(
      const std::string& device_key,
      base::OnceCallback<void(const std::optional<std::string>&)> callback)
      override;
  void ResetNotificationDeviceTracking() override;

  void AddObserver(InputDeviceSettingsController::Observer* observer) override;
  void RemoveObserver(
      InputDeviceSettingsController::Observer* observer) override;

  void OnKeyboardListUpdated(std::vector<ui::KeyboardDevice> keyboards_to_add,
                             std::vector<DeviceId> keyboard_ids_to_remove);
  void OnTouchpadListUpdated(std::vector<ui::TouchpadDevice> touchpads_to_add,
                             std::vector<DeviceId> touchpad_ids_to_remove);
  void OnMouseListUpdated(std::vector<ui::InputDevice> mice_to_add,
                          std::vector<DeviceId> mouse_ids_to_remove);
  void OnPointingStickListUpdated(
      std::vector<ui::InputDevice> pointing_sticks_to_add,
      std::vector<DeviceId> pointing_stick_ids_to_remove);
  void OnGraphicsTabletListUpdated(
      std::vector<ui::InputDevice> graphics_tablets_to_add,
      std::vector<DeviceId> graphics_tablet_ids_to_remove);
  const mojom::Keyboard* GetGeneralizedKeyboard();
  bool GetGeneralizedTopRowAreFKeys();
  void RestoreDefaultKeyboardRemappings(DeviceId id) override;

  // SessionObserver:
  void OnActiveUserPrefServiceChanged(PrefService* pref_service) override;

  void OnSessionStateChanged(session_manager::SessionState state) override;

  // input_method::InputMethodManager::Observer:
  void InputMethodChanged(input_method::InputMethodManager* manager,
                          Profile* profile,
                          bool show_message) override;

  // device::BluetoothAdapter::Observer:
  void DeviceBatteryChanged(device::BluetoothAdapter* adapter,
                            device::BluetoothDevice* device,
                            device::BluetoothDevice::BatteryType type) override;

  // LoginDataDispatcher::Observer:
  void OnOobeDialogStateChanged(OobeDialogState state) override;

  // apps::AppRegistryCache::Observer overrides:
  void OnAppUpdate(const apps::AppUpdate& update) override;
  void OnAppRegistryCacheWillBeDestroyed(
      apps::AppRegistryCache* cache) override;

  // message_center::MessageCenterObserver:
  void OnNotificationClicked(
      const std::string& notification_id,
      const std::optional<int>& button_index,
      const std::optional<std::u16string>& reply) override;

  InputDeviceDuplicateIdFinder& duplicate_id_finder() {
    CHECK(duplicate_id_finder_);
    return *duplicate_id_finder_;
  }

  void SetPeripheralsAppDelegate(PeripheralsAppDelegate* delegate);

  void AddWelcomeNotificationDeviceKeyForTesting(
      const std::string& device_key) {
    welcome_notification_clicked_device_keys_.insert(device_key);
  }

 private:
  void Init();

  void ScheduleDeviceSettingsRefresh();
  void RefreshAllDeviceSettings();
  void ShowFirstTimeConnectedNotifications();

  void RecordComboDeviceMetric(const mojom::Keyboard& keyboard);
  void RecordComboDeviceMetric(const mojom::Mouse& keyboard);

  void DispatchKeyboardConnected(DeviceId id);
  void DispatchKeyboardDisconnectedAndEraseFromList(DeviceId id);
  void DispatchKeyboardSettingsChanged(DeviceId id);

  void DispatchTouchpadConnected(DeviceId id);
  void DispatchTouchpadDisconnectedAndEraseFromList(DeviceId id);
  void DispatchTouchpadSettingsChanged(DeviceId id);

  void DispatchMouseConnected(DeviceId id);
  void DispatchMouseDisconnectedAndEraseFromList(DeviceId id);
  void DispatchMouseSettingsChanged(DeviceId id);

  void DispatchPointingStickConnected(DeviceId id);
  void DispatchPointingStickDisconnectedAndEraseFromList(DeviceId id);
  void DispatchPointingStickSettingsChanged(DeviceId id);

  void DispatchGraphicsTabletConnected(DeviceId id);
  void DispatchGraphicsTabletDisconnectedAndEraseFromList(DeviceId id);
  void DispatchGraphicsTabletSettingsChanged(DeviceId id);

  void DispatchCustomizableMouseButtonPressed(const mojom::Mouse& mouse,
                                              const mojom::Button& button);
  void DispatchCustomizableTabletButtonPressed(
      const mojom::GraphicsTablet& graphics_tablet,
      const mojom::Button& button);
  void DispatchCustomizablePenButtonPressed(
      const mojom::GraphicsTablet& graphics_tablet,
      const mojom::Button& button);

  void DispatchKeyboardBatteryInfoChanged(DeviceId id);
  void DispatchGraphicsTabletBatteryInfoChanged(DeviceId id);
  void DispatchMouseBatteryInfoChanged(DeviceId id);
  void DispatchTouchpadBatteryInfoChanged(DeviceId id);

  void InitializePolicyHandler();
  void OnKeyboardPoliciesChanged();
  void OnMousePoliciesChanged();

  // Correctly initializes settings depending on whether we have an active
  // user session or not.
  void InitializeGraphicsTabletSettings(mojom::GraphicsTablet* graphics_tablet);
  void InitializeKeyboardSettings(mojom::Keyboard* keyboard);
  void InitializeMouseSettings(mojom::Mouse* mouse);
  void InitializePointingStickSettings(mojom::PointingStick* pointing_stick);
  void InitializeTouchpadSettings(mojom::Touchpad* touchpad);

  // Update the cached per-user keyboard settings on the login screen using the
  // most recently connected internal/external device (if applicable). This
  // needs to be done in the following cases in order to keep our settings up
  // to date:
  // - A device is connected/disconnected.
  // - A user makes an update to a device setting.
  // - The active pref service changes.
  void RefreshStoredLoginScreenGraphicsTabletSettings();
  void RefreshStoredLoginScreenKeyboardSettings();
  void RefreshStoredLoginScreenMouseSettings();
  void RefreshStoredLoginScreenPointingStickSettings();
  void RefreshStoredLoginScreenTouchpadSettings();

  // Refreshes all internal settings. Called whenever prefs are updated.
  void RefreshInternalPointingStickSettings();
  void RefreshInternalTouchpadSettings();

  // Refreshes the settings for the device to match the default settings.
  void ForceInitializeDefaultChromeOSKeyboardSettings();
  void ForceInitializeDefaultNonChromeOSKeyboardSettings();
  void ForceInitializeDefaultSplitModifierKeyboardSettings();
  void ForceInitializeDefaultTouchpadSettings();
  void ForceInitializeDefaultMouseSettings();

  // Updates the default settings based on the most recently connected device.
  // This is called whenever a device is connected/disconnected or if settings
  // are updated.
  void RefreshMouseDefaultSettings();
  void RefreshKeyboardDefaultSettings();
  void RefreshTouchpadDefaultSettings();

  // Refreshes all cached settings which includes defaults and login screen
  // settings.
  void RefreshCachedMouseSettings();
  void RefreshCachedKeyboardSettings();
  void RefreshCachedTouchpadSettings();

  // Refreshes all companion app info for connected devices.
  void RefreshCompanionAppInfoForConnectedDevices();
  void OnCompanionAppInfoReceived(
      DeviceId id,
      const std::string& device_key,
      const std::optional<mojom::CompanionAppInfo>& info);

  void DispatchMouseCompanionAppInfoChanged(const mojom::Mouse& mouse);
  void DispatchKeyboardCompanionAppInfoChanged(const mojom::Keyboard& keyboard);
  void DispatchTouchpadCompanionAppInfoChanged(const mojom::Touchpad& touchpad);
  void DispatchGraphicsTabletCompanionAppInfoChanged(
      const mojom::GraphicsTablet& graphics_tablet);

  // Get the mouse customization restriction based on the mouse metadata. Return
  // kDisableKeyEventRewrites by default if there is no mouse metadata.
  mojom::CustomizationRestriction GetMouseCustomizationRestriction(
      const ui::InputDevice& mouse);

  // Get the graphics tablet customization restriction based on the graphics
  // tablet metadata. Return kAllowCustomizations by default if there is no
  // graphics tablet metadata.
  mojom::CustomizationRestriction GetGraphicsTabletCustomizationRestriction(
      const ui::InputDevice& graphics_tablet);

  // Refreshes the key display values within the button remappings to match the
  // current input method.
  void RefreshKeyDisplay();

  // Refresh meta and modifier keys when they potentially changed due to flags
  // being enabled.
  void RefreshMetaAndModifierKeys();

  // Get the mouse button config based on the mouse metadata. Return
  // kDefault by default if there is no mouse metadata.
  mojom::MouseButtonConfig GetMouseButtonConfig(const ui::InputDevice& mouse);

  // Get the graphics tablet button config based on the tablet metadata. Return
  // kDefault by default if there is no metadata.
  mojom::GraphicsTabletButtonConfig GetGraphicsTabletButtonConfig(
      const ui::InputDevice& graphics_tablet);

  // Determines whether a device image should be fetched.
  // Returns true if the following conditions are met:
  //  1. The welcome experience feature is enabled.
  //  2. An active account ID is available.
  //  3. An active preference service is available.
  bool ShouldFetchDeviceImage();

  // Initiates the process of fetching an image associated with a specific
  // input device.
  void GetDeviceImage(const std::string& device_key, DeviceId id);

  // Callback function triggered when a device image has been downloaded.
  // The DeviceId is used to identify the type of input device the image is
  // associated with.
  void OnDeviceNotificationImageDownloaded(DeviceId id,
                                           const DeviceImage& device_image);

  // Callback function triggered when a device image to be displayed in the
  // Settings UI has been downloaded.
  void OnDeviceImageForSettingsDownloaded(
      base::OnceCallback<void(const std::optional<std::string>&)> callback,
      const DeviceImage& device_image);

  mojom::Mouse* FindMouse(DeviceId id);
  mojom::Touchpad* FindTouchpad(DeviceId id);
  mojom::Keyboard* FindKeyboard(DeviceId id);
  mojom::GraphicsTablet* FindGraphicsTablet(DeviceId id);
  mojom::PointingStick* FindPointingStick(DeviceId id);

  void InitializeOnBluetoothReady(
      scoped_refptr<device::BluetoothAdapter> adapter);

  bool IsOobe() const;
  void RefreshBatteryInfoForConnectedDevices();

  base::ObserverList<InputDeviceSettingsController::Observer> observers_;

  std::unique_ptr<InputDeviceSettingsPolicyHandler> policy_handler_;

  raw_ptr<PrefService> local_state_ = nullptr;  // Not owned.

  std::unique_ptr<ModifierSplitBypassChecker> modifier_split_bypass_checker_;

  std::unique_ptr<KeyboardPrefHandler> keyboard_pref_handler_;
  std::unique_ptr<TouchpadPrefHandler> touchpad_pref_handler_;
  std::unique_ptr<MousePrefHandler> mouse_pref_handler_;
  std::unique_ptr<PointingStickPrefHandler> pointing_stick_pref_handler_;
  std::unique_ptr<GraphicsTabletPrefHandler> graphics_tablet_pref_handler_;

  base::flat_map<DeviceId, mojom::KeyboardPtr> keyboards_;
  base::flat_map<DeviceId, mojom::TouchpadPtr> touchpads_;
  base::flat_map<DeviceId, mojom::MousePtr> mice_;
  base::flat_map<DeviceId, mojom::PointingStickPtr> pointing_sticks_;
  base::flat_map<DeviceId, mojom::GraphicsTabletPtr> graphics_tablets_;
  // A map that stores associations between package IDs (e.g.,
  // "com.example.app") and the corresponding device IDs where the package is
  // installed or used. This map is used to track installations and removals for
  // devies with companion apps.
  base::flat_map<std::string, DeviceId> package_id_to_device_id_map_;
  // A set to track unique device keys of devices where the user clicked on the
  // welcome notification displayed during initial device connection.
  // This information is used for recording metrics:
  // - If a user modifies device settings AFTER clicking the notification,
  //   the presence of the device key in this set indicates the notification
  //   was seen before the setting change.
  // - This helps measure the impact of the welcome notification on user
  // behavior.
  base::flat_set<std::string> welcome_notification_clicked_device_keys_;

  // Notifiers must be declared after the `flat_map` objects as the notifiers
  // depend on these objects.
  std::unique_ptr<InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>
      keyboard_notifier_;
  std::unique_ptr<InputDeviceNotifier<mojom::TouchpadPtr, ui::TouchpadDevice>>
      touchpad_notifier_;
  std::unique_ptr<InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>>
      mouse_notifier_;
  std::unique_ptr<InputDeviceNotifier<mojom::PointingStickPtr, ui::InputDevice>>
      pointing_stick_notifier_;
  std::unique_ptr<
      InputDeviceNotifier<mojom::GraphicsTabletPtr, ui::InputDevice>>
      graphics_tablet_notifier_;
  std::unique_ptr<InputDeviceSettingsMetricsManager> metrics_manager_;

  std::unique_ptr<InputDeviceDuplicateIdFinder> duplicate_id_finder_;

  std::unique_ptr<InputDeviceSettingsNotificationController>
      notification_controller_;

  std::unique_ptr<InputDeviceSettingsMetadataManager> metadata_manager_;
  // Observe bluetooth device change events.
  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;

  raw_ptr<PrefService> active_pref_service_ = nullptr;  // Not owned.
  raw_ptr<PeripheralsAppDelegate> delegate_ = nullptr;  // Not owned.
  std::optional<AccountId> active_account_id_;
  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;

  // Boolean which notes whether or not there is a settings update in progress.
  bool settings_refresh_pending_ = false;

  OobeDialogState oobe_state_ = OobeDialogState::HIDDEN;
  base::ScopedObservation<apps::AppRegistryCache,
                          apps::AppRegistryCache::Observer>
      app_registry_cache_observer_{this};

  session_manager::SessionState last_session_ =
      session_manager::SessionState::UNKNOWN;

  // Task runner where settings refreshes are scheduled to run.
  scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
  base::WeakPtrFactory<InputDeviceSettingsControllerImpl> weak_ptr_factory_{
      this};
};

}  // namespace ash

#endif  // ASH_SYSTEM_INPUT_DEVICE_SETTINGS_INPUT_DEVICE_SETTINGS_CONTROLLER_IMPL_H_