chromium/ash/system/bluetooth/hid_preserving_controller/hid_preserving_bluetooth_state_controller.cc

// Copyright 2024 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/bluetooth/hid_preserving_controller/hid_preserving_bluetooth_state_controller.h"

#include "ash/constants/ash_features.h"
#include "ash/public/mojom/hid_preserving_bluetooth_state_controller.mojom.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/shell.h"
#include "ash/system/bluetooth/hid_preserving_controller/disable_bluetooth_dialog_controller_impl.h"
#include "ash/system/bluetooth/hid_preserving_controller/hid_preserving_bluetooth_metrics.h"
#include "ash/system/input_device_settings/input_device_settings_controller_impl.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "components/device_event_log/device_event_log.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"

namespace ash {

HidPreservingBluetoothStateController::HidPreservingBluetoothStateController() {
  CHECK(features::IsBluetoothDisconnectWarningEnabled());

  // Asynchronously bind to CrosBluetoothConfig so that we don't attempt
  // to bind to it before it has initialized.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &HidPreservingBluetoothStateController::BindToCrosBluetoothConfig,
          weak_ptr_factory_.GetWeakPtr()));
}

HidPreservingBluetoothStateController::
    ~HidPreservingBluetoothStateController() = default;

void HidPreservingBluetoothStateController::BindPendingReceiver(
    mojo::PendingReceiver<mojom::HidPreservingBluetoothStateController>
        pending_receiver) {
  receivers_.Add(this, std::move(pending_receiver));
}

void HidPreservingBluetoothStateController::TryToSetBluetoothEnabledState(
    bool enabled,
    mojom::HidWarningDialogSource source) {
  // If we are not disabling Bluetooth, no action is needed.
  if (enabled) {
    SetBluetoothEnabledState(enabled);
    return;
  }

  device_names_ = GetBluetoothDeviceNamesIfOnlyHids();
  if (device_names_.empty()) {
    BLUETOOTH_LOG(DEBUG) << "No Bluetooth devices found, disabling Bluetooth";
    SetBluetoothEnabledState(enabled);
    bluetooth::RecordHidPoweredStateDisableBehavior(/*dialog_shown=*/false);
    return;
  }

  if (!disable_bluetooth_dialog_controller_) {
    disable_bluetooth_dialog_controller_ =
        std::make_unique<DisableBluetoothDialogControllerImpl>();
  }

  bluetooth::RecordHidWarningDialogSource(source);
  bluetooth::RecordHidPoweredStateDisableBehavior(/*dialog_shown=*/true);
  BLUETOOTH_LOG(EVENT)
      << "Showing warning dialog: number of Bluetooth HID devices connected: "
      << device_names_.size();
  disable_bluetooth_dialog_controller_->ShowDialog(
      device_names_,
      base::BindOnce(&HidPreservingBluetoothStateController::OnShowCallback,
                     weak_ptr_factory_.GetWeakPtr(), enabled));
}

void HidPreservingBluetoothStateController::OnShowCallback(
    bool enabled,
    bool show_dialog_result) {
  BLUETOOTH_LOG(USER) << "Warning dialog result: " << show_dialog_result;
  bluetooth::RecordHidWarningUserAction(show_dialog_result);
  // User decided not to disable bluetooth.
  if (!show_dialog_result) {
    return;
  }

  SetBluetoothEnabledState(enabled);
}

DisableBluetoothDialogController::DeviceNamesList
HidPreservingBluetoothStateController::GetBluetoothDeviceNamesIfOnlyHids() {
  // TODO(b/333439577): Update this to only use
  // InputDeviceSettingsControllerImpl API.
  ui::DeviceDataManager* device_data_manager =
      ui::DeviceDataManager::GetInstance();
  InputDeviceSettingsControllerImpl* input_device_settings_controller =
      Shell::Get()->input_device_settings_controller();
  DisableBluetoothDialogController::DeviceNamesList bluetooth_devices;

  for (const auto& touchscreen : device_data_manager->GetTouchscreenDevices()) {
    // A non-Bluetooth HID is connected, return an empty list.
    if (touchscreen.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth touchscreen found: "
                           << touchscreen.name << " Type: " << touchscreen.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(touchscreen.name);
  }

  for (const auto& graphics_tablet :
       device_data_manager->GetGraphicsTabletDevices()) {
    // If graphics tablet device is not in list of expected devices do not
    // consider this device. see (b/332394154)
    const mojom::GraphicsTablet* found_device =
        input_device_settings_controller->GetGraphicsTablet(graphics_tablet.id);

    if (found_device == nullptr) {
      continue;
    }

    // A non-Bluetooth HID is connected, return an empty list.
    if (graphics_tablet.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth graphics tablet found: "
                           << graphics_tablet.name
                           << ", Type: " << graphics_tablet.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(graphics_tablet.name);
  }

  for (const auto& pointing_stick :
       device_data_manager->GetPointingStickDevices()) {
    // If pointing stick device is not in list of expected devices do not
    // consider this device. see (b/332394154)
    const mojom::PointingStick* found_device =
        input_device_settings_controller->GetPointingStick(pointing_stick.id);

    if (found_device == nullptr) {
      continue;
    }

    // A non-Bluetooth HID is connected, return an empty list.
    if (pointing_stick.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth pointing_stick found: "
                           << pointing_stick.name
                           << " Type: " << pointing_stick.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(pointing_stick.name);
  }

  for (const auto& touchpad : device_data_manager->GetTouchpadDevices()) {
    // If touchpad device is not in list of expected devices do not consider
    // this device. see (b/332394154)
    const mojom::Touchpad* found_device =
        input_device_settings_controller->GetTouchpad(touchpad.id);

    if (found_device == nullptr) {
      continue;
    }

    // A non-Bluetooth HID is connected, return an empty list.
    if (touchpad.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth keyboard found: " << touchpad.name
                           << " Type: " << touchpad.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(touchpad.name);
  }

  for (const auto& keyboard : device_data_manager->GetKeyboardDevices()) {
    // If keyboard device is not in list of expected devices do not consider
    // this device. see (b/332394154)
    const mojom::Keyboard* found_device =
        input_device_settings_controller->GetKeyboard(keyboard.id);

    if (found_device == nullptr) {
      continue;
    }

    // A non-Bluetooth HID is connected, return an empty list.
    if (keyboard.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth keyboard found: " << keyboard.name
                           << " Type: " << keyboard.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(keyboard.name);
  }

  for (const auto& mice : device_data_manager->GetMouseDevices()) {
    // If mouse device is not in list of expected devices do not consider this
    // device. see (b/332394154)
    const mojom::Mouse* found_device =
        input_device_settings_controller->GetMouse(mice.id);

    if (found_device == nullptr) {
      continue;
    }

    // A non-Bluetooth HID is connected, return an empty list.
    if (mice.type != ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
      BLUETOOTH_LOG(DEBUG) << "Non-Bluetooth mouse found: " << mice.name
                           << ", Type: " << mice.type;
      return DisableBluetoothDialogController::DeviceNamesList();
    }
    bluetooth_devices.push_back(mice.name);
  }
  return bluetooth_devices;
}

void HidPreservingBluetoothStateController::SetBluetoothEnabledState(
    bool enabled) {
  CHECK(cros_bluetooth_config_remote_);
  cros_bluetooth_config_remote_->SetBluetoothEnabledState(enabled);
}

void HidPreservingBluetoothStateController::BindToCrosBluetoothConfig() {
  GetBluetoothConfigService(
      cros_bluetooth_config_remote_.BindNewPipeAndPassReceiver());
}

void HidPreservingBluetoothStateController::
    SetDisableBluetoothDialogControllerForTest(
        std::unique_ptr<ash::DisableBluetoothDialogController> controller) {
  disable_bluetooth_dialog_controller_ = std::move(controller);
}

DisableBluetoothDialogController*
HidPreservingBluetoothStateController::GetDisabledBluetoothDialogForTesting() {
  CHECK(disable_bluetooth_dialog_controller_);
  return disable_bluetooth_dialog_controller_.get();
}
}  // namespace ash