chromium/ash/system/bluetooth/bluetooth_device_status_ui_handler.cc

// Copyright 2021 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/bluetooth_device_status_ui_handler.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/bluetooth_config_service.h"
#include "ash/public/cpp/hats_bluetooth_revamp_trigger.h"
#include "ash/public/cpp/system/toast_data.h"
#include "ash/public/cpp/system/toast_manager.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h"
#include "components/device_event_log/device_event_log.h"
#include "components/prefs/pref_service.h"
#include "device/bluetooth/chromeos/bluetooth_utils.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

namespace {

using bluetooth_config::GetPairedDeviceName;
using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;

const char kBluetoothToastIdPrefix[] = "cros_bluetooth_device_toast_id-";

}  // namespace

BluetoothDeviceStatusUiHandler::BluetoothDeviceStatusUiHandler(
    PrefService* local_state)
    : local_state_(local_state) {
  // Asynchronously bind to CrosBluetoothConfig so that we don't want to attempt
  // to bind to it before it has initialized.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&BluetoothDeviceStatusUiHandler::BindToCrosBluetoothConfig,
                     weak_ptr_factory_.GetWeakPtr()));
  // `local_state` may be null in tests.
  if (!local_state_) {
    return;
  }
  device::MaybeRecordConnectionToastShownCount(local_state_,
                                               /*triggered_by_connect=*/false);
}

BluetoothDeviceStatusUiHandler::~BluetoothDeviceStatusUiHandler() = default;

// static:
void BluetoothDeviceStatusUiHandler::RegisterLocalStatePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterIntegerPref(prefs::kBluetoothConnectionToastShownCount, 0);
  registry->RegisterTimePref(prefs::kBluetoothToastCountStartTime,
                             base::Time::Now().LocalMidnight());
}

void BluetoothDeviceStatusUiHandler::OnDevicePaired(
    PairedBluetoothDevicePropertiesPtr device) {
  BLUETOOTH_LOG(EVENT) << "Notifying device was paired: "
                       << device->device_properties->id;
  ash::ToastData toast_data(
      /*id=*/GetToastId(device.get()),
      ash::ToastCatalogName::kBluetoothDevicePaired,
      /*text=*/
      l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIRED_OR_CONNECTED_TOAST,
          GetPairedDeviceName(device)));

  ShowToast(std::move(toast_data));
  device::RecordUiSurfaceDisplayed(device::BluetoothUiSurface::kPairedToast);
}

void BluetoothDeviceStatusUiHandler::OnDeviceDisconnected(
    PairedBluetoothDevicePropertiesPtr device) {
  BLUETOOTH_LOG(EVENT) << "Notifying device was disconnected: "
                       << device->device_properties->id;

  ash::ToastData toast_data(
      /*id=*/GetToastId(device.get()),
      ash::ToastCatalogName::kBluetoothDeviceDisconnected,
      /*text=*/
      l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCONNECTED_TOAST,
          GetPairedDeviceName(device)));
  ShowToast(std::move(toast_data));
  device::RecordUiSurfaceDisplayed(
      device::BluetoothUiSurface::kDisconnectedToast);
}

void BluetoothDeviceStatusUiHandler::OnDeviceConnected(
    PairedBluetoothDevicePropertiesPtr device) {
  BLUETOOTH_LOG(EVENT) << "Notifying device was connected: "
                       << device->device_properties->id;

  ash::ToastData toast_data(
      /*id=*/GetToastId(device.get()),
      ash::ToastCatalogName::kBluetoothDeviceConnected,
      /*text=*/
      l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIRED_OR_CONNECTED_TOAST,
          GetPairedDeviceName(device)));
  ShowToast(std::move(toast_data));
  device::RecordUiSurfaceDisplayed(
      device::BluetoothUiSurface::kConnectionToast);
  device::MaybeRecordConnectionToastShownCount(local_state_,
                                               /*triggered_by_connect=*/true);

  if (last_connection_timestamp_.has_value()) {
    device::RecordTimeIntervalBetweenConnections(
        base::TimeTicks::Now() - last_connection_timestamp_.value());
  }
  last_connection_timestamp_ = base::TimeTicks::Now();

  if (auto* hats_bluetooth_revamp_trigger = HatsBluetoothRevampTrigger::Get()) {
    hats_bluetooth_revamp_trigger->TryToShowSurvey();
  }
}

void BluetoothDeviceStatusUiHandler::ShowToast(ash::ToastData toast_data) {
  ash::ToastManager::Get()->Show(std::move(toast_data));
}

std::string BluetoothDeviceStatusUiHandler::GetToastId(
    const bluetooth_config::mojom::PairedBluetoothDeviceProperties*
        paired_device_properties) {
  return kBluetoothToastIdPrefix +
         base::ToLowerASCII(paired_device_properties->device_properties->id);
}

void BluetoothDeviceStatusUiHandler::BindToCrosBluetoothConfig() {
  GetBluetoothConfigService(
      remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver());
  remote_cros_bluetooth_config_->ObserveDeviceStatusChanges(
      cros_bluetooth_device_status_observer_receiver_
          .BindNewPipeAndPassRemote());
}

}  // namespace ash