chromium/chrome/browser/ash/chromebox_for_meetings/device_info/device_info_service.cc

// Copyright 2020 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/chromebox_for_meetings/device_info/device_info_service.h"

#include <cstdint>
#include <optional>
#include <string_view>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/common/channel_info.h"
#include "chromeos/ash/components/dbus/chromebox_for_meetings/cfm_hotline_client.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/version_info/version_info.h"
#include "mojo/public/cpp/bindings/receiver_set.h"

namespace ash::cfm {

namespace {

// TODO(https://crbug.com/1403174): Remove when namespace of mojoms for CfM are
// migarted to ash.
namespace mojom = ::chromeos::cfm::mojom;

constexpr char kRootPartition[] = "/";
constexpr char kStatefulPartition[] = "/mnt/stateful_partition";
constexpr char kReleaseVersion[] = "CHROMEOS_RELEASE_VERSION";
constexpr char kReleasBuildType[] = "CHROMEOS_RELEASE_BUILD_TYPE";
constexpr char kReleaseTrack[] = "CHROMEOS_RELEASE_TRACK";
constexpr char kReleaseChromeMilestone[] = "CHROMEOS_RELEASE_CHROME_MILESTONE";
constexpr char kReleaseBoard[] = "CHROMEOS_RELEASE_BOARD";

static DeviceInfoService* g_info_service = nullptr;

}  // namespace

// static
void DeviceInfoService::Initialize() {
  CHECK(!g_info_service);
  g_info_service = new DeviceInfoService();
}

// static
void DeviceInfoService::Shutdown() {
  CHECK(g_info_service);
  delete g_info_service;
  g_info_service = nullptr;
}

// static
DeviceInfoService* DeviceInfoService::Get() {
  CHECK(g_info_service)
      << "DeviceInfoService::Get() called before Initialize()";
  return g_info_service;
}

// static
bool DeviceInfoService::IsInitialized() {
  return g_info_service;
}

bool DeviceInfoService::ServiceRequestReceived(
    const std::string& interface_name) {
  if (interface_name != mojom::MeetDevicesInfo::Name_) {
    return false;
  }

  service_adaptor_.BindServiceAdaptor();
  return true;
}

void DeviceInfoService::OnBindService(
    mojo::ScopedMessagePipeHandle receiver_pipe) {
  receivers_.Add(this, mojo::PendingReceiver<mojom::MeetDevicesInfo>(
                           std::move(receiver_pipe)));
}

void DeviceInfoService::OnAdaptorConnect(bool success) {
  if (!success) {
    LOG(ERROR) << "mojom::DeviceInfo Service Adaptor connection failed.";
    return;
  }

  VLOG(3) << "mojom::DeviceInfo Service Adaptor is connected.";
  CHECK(DeviceSettingsService::IsInitialized());
  DeviceSettingsService::Get()->AddObserver(this);
}

void DeviceInfoService::OnAdaptorDisconnect() {
  LOG(WARNING) << "mojom::DeviceInfo Service Adaptor has been disconnected";
  Reset();
}

void DeviceInfoService::DeviceSettingsUpdated() {
  // Post to primary task runner
  task_runner_->PostTask(FROM_HERE,
                         base::BindOnce(&DeviceInfoService::UpdatePolicyInfo,
                                        weak_ptr_factory_.GetWeakPtr()));
}

void DeviceInfoService::OnDeviceSettingsServiceShutdown() {
  // Post to primary task runner
  task_runner_->PostTask(FROM_HERE,
                         base::BindOnce(&DeviceInfoService::Reset,
                                        weak_ptr_factory_.GetWeakPtr()));
}

void DeviceInfoService::AddDeviceSettingsObserver(
    ::mojo::PendingRemote<mojom::PolicyInfoObserver> observer) {
  mojo::Remote<mojom::PolicyInfoObserver> info_observer(std::move(observer));
  if (!current_policy_info_.is_null()) {
    info_observer->OnPolicyInfoChange(current_policy_info_->Clone());
  }

  policy_remotes_.Add(std::move(info_observer));
}

void DeviceInfoService::UpdatePolicyInfo() {
  auto policy_info = mojom::PolicyInfo::New();

  PopulatePolicyInfoFromProto(policy_info);
  PopulateChromeDeviceSettingsFromProto(policy_info);

  if (current_policy_info_.Equals(policy_info)) {
    return;
  }

  current_policy_info_ = std::move(policy_info);

  for (auto& remote : policy_remotes_) {
    remote->OnPolicyInfoChange(current_policy_info_->Clone());
  }
}

void DeviceInfoService::PopulatePolicyInfoFromProto(
    mojom::PolicyInfoPtr& policy_info) {
  auto* device_settings = DeviceSettingsService::Get();

  if (!device_settings || !device_settings->policy_data()) {
    return;
  }

  auto* policy_data = device_settings->policy_data();

  if (policy_data->has_timestamp()) {
    policy_info->timestamp_ms = policy_data->timestamp();
  }

  if (policy_data->has_directory_api_id()) {
    policy_info->device_id = policy_data->directory_api_id();
  }

  if (policy_data->has_service_account_identity()) {
    policy_info->service_account_email_address =
        policy_data->service_account_identity();
  }

  if (policy_data->has_gaia_id()) {
    base::StringToInt64(policy_data->gaia_id(),
                        &policy_info->service_account_gaia_id);
  }

  if (policy_data->has_device_id()) {
    policy_info->cros_device_id = policy_data->device_id();
  }
}

void DeviceInfoService::PopulateChromeDeviceSettingsFromProto(
    mojom::PolicyInfoPtr& policy_info) {
  auto* device_settings = DeviceSettingsService::Get();

  if (!device_settings || !device_settings->device_settings()) {
    return;
  }

  auto* chrome_settings_data = device_settings->device_settings();

  if (chrome_settings_data->has_auto_update_settings()) {
    auto auto_update_settings = chrome_settings_data->auto_update_settings();

    if (auto_update_settings.has_device_quick_fix_build_token()) {
      policy_info->cohort_hint =
          auto_update_settings.device_quick_fix_build_token();
    }
  }

  if (chrome_settings_data->has_release_channel()) {
    auto release_channel = chrome_settings_data->release_channel();

    if (release_channel.has_release_channel_delegated()) {
      policy_info->release_channel_delegated =
          release_channel.release_channel_delegated();
    }
  }
}

void DeviceInfoService::GetPolicyInfo(GetPolicyInfoCallback callback) {
  if (!current_policy_info_.is_null()) {
    std::move(callback).Run(current_policy_info_->Clone());
  } else {
    std::move(callback).Run(nullptr);
  }
}

void DeviceInfoService::GetSysInfo(GetSysInfoCallback callback) {
  auto root = base::FilePath(kRootPartition);
  auto stateful = base::FilePath(kStatefulPartition);

  auto sys_info = mojom::SysInfo::New();
  sys_info->kernel_version = base::SysInfo::KernelVersion();

  std::string value;
  if (base::SysInfo::GetLsbReleaseValue(kReleaseVersion, &value)) {
    sys_info->release_version = std::move(value);
  }
  if (base::SysInfo::GetLsbReleaseValue(kReleasBuildType, &value)) {
    sys_info->release_build_type = std::move(value);
  }
  if (base::SysInfo::GetLsbReleaseValue(kReleaseTrack, &value)) {
    sys_info->release_track = std::move(value);
  }
  if (base::SysInfo::GetLsbReleaseValue(kReleaseChromeMilestone, &value)) {
    sys_info->release_milestone = std::move(value);
  }
  if (base::SysInfo::GetLsbReleaseValue(kReleaseBoard, &value)) {
    sys_info->release_board = std::move(value);
  }

  sys_info->browser_version = version_info::GetVersionNumber();
  sys_info->channel_name = version_info::GetChannelString(chrome::GetChannel());

  std::move(callback).Run(std::move(sys_info));
}

void DeviceInfoService::GetMachineStatisticsInfo(
    GetMachineStatisticsInfoCallback callback) {
  if (!on_machine_statistics_loaded_) {
    return std::move(callback).Run(nullptr);
  }

  auto stat_info = mojom::MachineStatisticsInfo::New();

  if (const std::optional<std::string_view> hwid =
          system::StatisticsProvider::GetInstance()->GetMachineStatistic(
              system::kHardwareClassKey)) {
    stat_info->hwid = std::string(hwid.value());
  }

  std::move(callback).Run(std::move(stat_info));
}

// Private methods

DeviceInfoService::DeviceInfoService()
    : service_adaptor_(mojom::MeetDevicesInfo::Name_, this),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      on_machine_statistics_loaded_(false) {
  CfmHotlineClient::Get()->AddObserver(this);
  current_policy_info_.reset();
  // Device settings update may not be triggered in some cases
  DeviceSettingsUpdated();
  // Wait for machine statistics to be loaded
  ScheduleOnMachineStatisticsLoaded();
}

DeviceInfoService::~DeviceInfoService() {
  CfmHotlineClient::Get()->RemoveObserver(this);
  Reset();
}

void DeviceInfoService::ScheduleOnMachineStatisticsLoaded() {
  // GetMachineStatistic() will block if called before statistics have been
  // loaded. To avoid this we gate collection until this callback occurs.

  on_machine_statistics_loaded_ = false;

  system::StatisticsProvider::GetInstance()->ScheduleOnMachineStatisticsLoaded(
      base::BindOnce(&DeviceInfoService::SetOnMachineStatisticsLoaded,
                     weak_ptr_factory_.GetWeakPtr(), true));
}

void DeviceInfoService::SetOnMachineStatisticsLoaded(bool loaded) {
  on_machine_statistics_loaded_ = loaded;
}

void DeviceInfoService::Reset() {
  receivers_.Clear();
  policy_remotes_.Clear();
  DeviceSettingsService::Get()->RemoveObserver(this);
}

}  // namespace ash::cfm