chromium/chrome/browser/ash/bluetooth/debug_logs_manager.cc

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

#include "chrome/browser/ash/bluetooth/debug_logs_manager.h"

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/dbus/bluetooth_debug_manager_client.h"
#include "device/bluetooth/dbus/bluez_dbus_manager.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
#include "device/bluetooth/floss/floss_dbus_manager.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/floss/floss_logging_client.h"
#include "google_apis/gaia/gaia_auth_util.h"

namespace ash {

namespace bluetooth {

namespace {
const char kVerboseLoggingEnablePrefName[] = "bluetooth.verboseLogging.enable";

const uint8_t kVerboseDisabledLevel = 0;
const uint8_t kVerboseBasicLevel = 1;

const int kDbusRetryCount = 10;
constexpr base::TimeDelta kDbusRetryInterval = base::Seconds(3);
}  // namespace

DebugLogsManager::DebugLogsManager(const std::string& primary_user_email,
                                   PrefService* pref_service)
    : primary_user_email_(primary_user_email), pref_service_(pref_service) {
  // For Googlers, set the default preference of Bluetooth verbose logs to true.
  if (AreDebugLogsSupported() &&
      !pref_service->HasPrefPath(kVerboseLoggingEnablePrefName)) {
    ChangeDebugLogsState(true);
  }

  SetVerboseLogsEnable(GetDebugLogsState() ==
                       DebugLogsState::kSupportedAndEnabled);

  SetBluetoothQualityReport(
      /*enable=*/features::IsBluetoothQualityReportEnabled(),
      /*num_completed_attempts=*/0);

  // Grab the Bluetooth adapter instance so we can register observers.
  device::BluetoothAdapterFactory::Get()->GetAdapter(
      base::BindOnce(&DebugLogsManager::OnBluetoothAdapterAvailable,
                     weak_ptr_factory_.GetWeakPtr()));
}

DebugLogsManager::~DebugLogsManager() {
  if (adapter_) {
    adapter_->RemoveObserver(this);
    adapter_.reset();
  }

  SetVerboseLogsEnable(false);

  // Disable Bluetooth Quality Report if it is enabled on login.
  if (features::IsBluetoothQualityReportEnabled())
    SetBluetoothQualityReport(/*enable=*/false,
                              /*num_completed_attempts=*/0);
}

// static
void DebugLogsManager::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(kVerboseLoggingEnablePrefName,
                                /*default_value=*/false);
}

DebugLogsManager::DebugLogsState DebugLogsManager::GetDebugLogsState() const {
  if (!AreDebugLogsSupported())
    return DebugLogsState::kNotSupported;

  return pref_service_->GetBoolean(kVerboseLoggingEnablePrefName)
             ? DebugLogsState::kSupportedAndEnabled
             : DebugLogsState::kSupportedButDisabled;
}

mojo::PendingRemote<mojom::DebugLogsChangeHandler>
DebugLogsManager::GenerateRemote() {
  mojo::PendingRemote<mojom::DebugLogsChangeHandler> remote;
  receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

void DebugLogsManager::ChangeDebugLogsState(bool should_debug_logs_be_enabled) {
  DCHECK_NE(GetDebugLogsState(), DebugLogsState::kNotSupported);

  pref_service_->SetBoolean(kVerboseLoggingEnablePrefName,
                            should_debug_logs_be_enabled);
}

bool DebugLogsManager::AreDebugLogsSupported() const {
  if (!base::FeatureList::IsEnabled(features::kShowBluetoothDebugLogToggle))
    return false;

  return gaia::IsGoogleInternalAccountEmail(primary_user_email_);
}

void DebugLogsManager::SetVerboseLogsEnable(bool enable) {
  debug_logs_enabled_ = enable;
  SendDBusVerboseLogsMessage(enable, /*num_completed_attempts=*/0);
}

void DebugLogsManager::SendDBusVerboseLogsMessage(bool enable,
                                                  int num_completed_attempts) {
  uint8_t level = enable ? kVerboseBasicLevel : kVerboseDisabledLevel;
  VLOG(1) << (enable ? "Enabling" : "Disabling") << " bluetooth verbose logs";

  if (floss::features::IsFlossEnabled()) {
    if (adapter_ && adapter_->IsPowered()) {
      floss::FlossDBusManager::Get()->GetLoggingClient()->SetDebugLogging(
          base::BindOnce(&DebugLogsManager::OnFlossSetDebugLogging,
                         weak_ptr_factory_.GetWeakPtr(), enable,
                         num_completed_attempts),
          enable);
    }
  } else {
    bluez::BluezDBusManager::Get()
        ->GetBluetoothDebugManagerClient()
        ->SetLogLevels(
            /*bluez_level=*/level, /*kernel_level=*/0,
            base::BindOnce(&DebugLogsManager::OnVerboseLogsEnableSuccess,
                           weak_ptr_factory_.GetWeakPtr(), enable),
            base::BindOnce(&DebugLogsManager::OnVerboseLogsEnableError,
                           weak_ptr_factory_.GetWeakPtr(), enable,
                           num_completed_attempts));
  }
}

void DebugLogsManager::OnFlossSetDebugLogging(
    const bool enable,
    const int num_completed_attempts,
    floss::DBusResult<floss::Void> result) {
  if (result.has_value()) {
    OnVerboseLogsEnableSuccess(enable);
  } else {
    auto error = result.error();
    OnVerboseLogsEnableError(enable, num_completed_attempts, error.name,
                             error.message);
  }
}

void DebugLogsManager::OnVerboseLogsEnableSuccess(const bool enable) {
  VLOG(1) << "Bluetooth verbose logs successfully "
          << (enable ? "enabled" : "disabled");
}

void DebugLogsManager::OnVerboseLogsEnableError(
    const bool enable,
    const int num_completed_attempts,
    const std::string& error_name,
    const std::string& error_message) {
  bool should_retry = (num_completed_attempts < kDbusRetryCount);

  LOG(ERROR) << "Setting bluetooth verbose logs failed: error: " << error_name
             << " - " << error_message << " "
             << (should_retry ? "Retrying." : "Giving up.");

  if (!should_retry)
    return;

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&DebugLogsManager::SendDBusVerboseLogsMessage,
                     weak_ptr_factory_.GetWeakPtr(), enable,
                     num_completed_attempts + 1),
      kDbusRetryInterval);
}

void DebugLogsManager::SetBluetoothQualityReport(bool enable,
                                                 int num_completed_attempts) {
  VLOG(1) << (enable ? "Enabling" : "Disabling") << " Bluetooth Quality Report";

  if (floss::features::IsFlossEnabled()) {
    VLOG(1) << "Floss does not yet support Bluetooth Quality Report.";
    return;
  }

  bluez::BluezDBusManager::Get()
      ->GetBluetoothDebugManagerClient()
      ->SetBluetoothQualityReport(
          enable,
          base::BindOnce(&DebugLogsManager::OnSetBluetoothQualityReportSuccess,
                         weak_ptr_factory_.GetWeakPtr(), enable),
          base::BindOnce(&DebugLogsManager::OnSetBluetoothQualityReportError,
                         weak_ptr_factory_.GetWeakPtr(), enable,
                         num_completed_attempts));
}

void DebugLogsManager::OnSetBluetoothQualityReportSuccess(bool enable) {
  VLOG(1) << "Bluetooth Quality Report successfully "
          << (enable ? "enabled" : "disabled");
}

void DebugLogsManager::OnSetBluetoothQualityReportError(
    const bool enable,
    const int num_completed_attempts,
    const std::string& error_name,
    const std::string& error_message) {
  bool should_retry = (num_completed_attempts < kDbusRetryCount);

  LOG(ERROR) << "Setting Bluetooth Quality Report failed: error: " << error_name
             << " - " << error_message << " "
             << (should_retry ? "Retrying." : "Giving up.");

  if (!should_retry)
    return;

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&DebugLogsManager::SetBluetoothQualityReport,
                     weak_ptr_factory_.GetWeakPtr(), enable,
                     num_completed_attempts + 1),
      kDbusRetryInterval);
}

void DebugLogsManager::OnBluetoothAdapterAvailable(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  adapter_ = std::move(adapter);
  adapter_->AddObserver(this);
}

void DebugLogsManager::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
                                             bool powered) {
  // Bluez does not dynamically set log level (it is persistent).
  if (!floss::features::IsFlossEnabled()) {
    return;
  }

  // We only need to send enable call when powering on and we want to enable
  // debug logs. We don't need to turn it off on power on.
  if (powered && debug_logs_enabled_) {
    SendDBusVerboseLogsMessage(debug_logs_enabled_,
                               /*num_completed_attempts=*/0);
  }
}

}  // namespace bluetooth

}  // namespace ash