chromium/chrome/browser/metrics/chromeos_system_profile_provider.cc

// 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.

#include "chrome/browser/metrics/chromeos_system_profile_provider.h"

#include <string_view>

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/thread_pool.h"
#include "base/version.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_dimensions.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/login/users/chrome_user_manager_util.h"
#include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "components/metrics/structured/recorder.h"
#include "components/prefs/pref_service.h"
#include "components/variations/hashing.h"
#include "third_party/metrics_proto/system_profile.pb.h"
#include "ui/display/display.h"
#include "ui/events/event_utils.h"

namespace {

// Called on a background thread to load cellular device variant
// using ConfigFS.
std::string GetCellularDeviceVariantOnBackgroundThread() {
  constexpr char kFirmwareVariantPath[] =
      "/run/chromeos-config/v1/modem/firmware-variant";
  std::string cellular_device_variant;
  const base::FilePath modem_path = base::FilePath(kFirmwareVariantPath);
  if (base::PathExists(modem_path)) {
    base::ReadFileToString(modem_path, &cellular_device_variant);
  }
  VLOG(1) << "cellular_device_variant: " << cellular_device_variant;
  return cellular_device_variant;
}

bool IsFeatureEnabled(
    const ash::multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
        feature_states_map,
    ash::multidevice_setup::mojom::Feature feature) {
  return feature_states_map.find(feature)->second ==
         ash::multidevice_setup::mojom::FeatureState::kEnabledByUser;
}

}  // namespace

ChromeOSSystemProfileProvider::ChromeOSSystemProfileProvider()
    : cached_profile_(std::make_unique<metrics::CachedMetricsProfile>()),
      weak_ptr_factory_(this) {}

ChromeOSSystemProfileProvider::~ChromeOSSystemProfileProvider() = default;

void ChromeOSSystemProfileProvider::AsyncInit(base::OnceClosure callback) {
  base::RepeatingClosure barrier = base::BarrierClosure(4, std::move(callback));
  InitTaskGetFullHardwareClass(barrier);
  InitTaskGetArcFeatures(barrier);
  InitTaskGetTpmFirmwareVersion(barrier);
  InitTaskGetCellularDeviceVariant(barrier);
}

void ChromeOSSystemProfileProvider::OnDidCreateMetricsLog() {
  registered_user_count_at_log_initialization_ = false;
  if (user_manager::UserManager::IsInitialized()) {
    registered_user_count_at_log_initialization_ = true;
    user_count_at_log_initialization_ =
        user_manager::UserManager::Get()->GetLoggedInUsers().size();
  }
}

void ChromeOSSystemProfileProvider::ProvideSystemProfileMetrics(
    metrics::SystemProfileProto* system_profile_proto) {
  WriteLinkedAndroidPhoneProto(system_profile_proto);
  UpdateMultiProfileUserCount(system_profile_proto);
  WriteDemoModeDimensionMetrics(system_profile_proto);

  metrics::SystemProfileProto::Hardware* hardware =
      system_profile_proto->mutable_hardware();
  hardware->set_full_hardware_class(full_hardware_class_);
  display::Display::TouchSupport has_touch =
      ui::GetInternalDisplayTouchSupport();
  if (has_touch == display::Display::TouchSupport::AVAILABLE)
    hardware->set_internal_display_supports_touch(true);
  else if (has_touch == display::Display::TouchSupport::UNAVAILABLE)
    hardware->set_internal_display_supports_touch(false);

  if (tpm_rw_firmware_version_.has_value()) {
    hardware->set_tpm_rw_firmware_version(*tpm_rw_firmware_version_);
  }

  hardware->set_cellular_device_variant(cellular_device_variant_);

  if (arc_release_) {
    metrics::SystemProfileProto::OS::Arc* arc =
        system_profile_proto->mutable_os()->mutable_arc();
    arc->set_release(*arc_release_);
  }
}

void ChromeOSSystemProfileProvider::WriteLinkedAndroidPhoneProto(
    metrics::SystemProfileProto* system_profile_proto) {
  ash::multidevice_setup::MultiDeviceSetupClient* client =
      ash::multidevice_setup::MultiDeviceSetupClientFactory::GetForProfile(
          cached_profile_->GetMetricsProfile());

  if (!client)
    return;

  const ash::multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice&
      host_status_with_device = client->GetHostStatus();
  if (host_status_with_device.first !=
      ash::multidevice_setup::mojom::HostStatus::kHostVerified) {
    return;
  }

  metrics::SystemProfileProto::LinkedAndroidPhoneData*
      linked_android_phone_data =
          system_profile_proto->mutable_linked_android_phone_data();
  const uint32_t hashed_name =
      variations::HashName(host_status_with_device.second->pii_free_name());
  linked_android_phone_data->set_phone_model_name_hash(hashed_name);

  const ash::multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
      feature_states_map = client->GetFeatureStates();
  linked_android_phone_data->set_is_smartlock_enabled(IsFeatureEnabled(
      feature_states_map, ash::multidevice_setup::mojom::Feature::kSmartLock));
  linked_android_phone_data->set_is_instant_tethering_enabled(IsFeatureEnabled(
      feature_states_map,
      ash::multidevice_setup::mojom::Feature::kInstantTethering));
}

void ChromeOSSystemProfileProvider::UpdateMultiProfileUserCount(
    metrics::SystemProfileProto* system_profile_proto) {
  if (user_manager::UserManager::IsInitialized()) {
    size_t user_count =
        user_manager::UserManager::Get()->GetLoggedInUsers().size();

    // We invalidate the user count if it changed while the log was open.
    if (registered_user_count_at_log_initialization_ &&
        user_count != user_count_at_log_initialization_) {
      user_count = 0;
    }

    system_profile_proto->set_multi_profile_user_count(user_count);
  }
}

void ChromeOSSystemProfileProvider::WriteDemoModeDimensionMetrics(
    metrics::SystemProfileProto* system_profile_proto) {
  if (!ash::DemoSession::IsDeviceInDemoMode()) {
    return;
  }
  metrics::SystemProfileProto::DemoModeDimensions* demo_mode_dimensions =
      system_profile_proto->mutable_demo_mode_dimensions();
  demo_mode_dimensions->set_country(ash::demo_mode::Country());

  metrics::SystemProfileProto_DemoModeDimensions_Retailer* retailer =
      demo_mode_dimensions->mutable_retailer();
  retailer->set_retailer_id(ash::demo_mode::RetailerName());
  retailer->set_store_id(ash::demo_mode::StoreNumber());

  if (ash::demo_mode::IsCloudGamingDevice()) {
    demo_mode_dimensions->add_customization_facet(
        metrics::
            SystemProfileProto_DemoModeDimensions_CustomizationFacet_CLOUD_GAMING_DEVICE);
  }
  if (ash::demo_mode::IsFeatureAwareDevice()) {
    demo_mode_dimensions->add_customization_facet(
        metrics::
            SystemProfileProto_DemoModeDimensions_CustomizationFacet_FEATURE_AWARE_DEVICE);
  }

  demo_mode_dimensions->set_app_version(
      ash::demo_mode::AppVersion().GetString());
  demo_mode_dimensions->set_resources_version(
      ash::demo_mode::ResourcesVersion().GetString());
}

void ChromeOSSystemProfileProvider::InitTaskGetFullHardwareClass(
    base::OnceClosure callback) {
  ash::system::StatisticsProvider::GetInstance()
      ->ScheduleOnMachineStatisticsLoaded(base::BindOnce(
          &ChromeOSSystemProfileProvider::OnMachineStatisticsLoaded,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ChromeOSSystemProfileProvider::InitTaskGetArcFeatures(
    base::OnceClosure callback) {
  arc::ArcFeaturesParser::GetArcFeatures(
      base::BindOnce(&ChromeOSSystemProfileProvider::OnArcFeaturesParsed,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ChromeOSSystemProfileProvider::InitTaskGetTpmFirmwareVersion(
    base::OnceClosure callback) {
  chromeos::TpmManagerClient::Get()->GetVersionInfo(
      tpm_manager::GetVersionInfoRequest(),
      base::BindOnce(
          &ChromeOSSystemProfileProvider::OnTpmManagerGetRwVersionInfo,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ChromeOSSystemProfileProvider::InitTaskGetCellularDeviceVariant(
    base::OnceClosure callback) {
  // Run the (potentially expensive) task in the background to avoid blocking
  // the UI thread.
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::WithBaseSyncPrimitives(),
       base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&GetCellularDeviceVariantOnBackgroundThread),
      base::BindOnce(&ChromeOSSystemProfileProvider::SetCellularDeviceVariant,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ChromeOSSystemProfileProvider::OnMachineStatisticsLoaded(
    base::OnceClosure callback) {
  if (const std::optional<std::string_view> full_hardware_class =
          ash::system::StatisticsProvider::GetInstance()->GetMachineStatistic(
              "hardware_class")) {
    full_hardware_class_ = std::string(full_hardware_class.value());
  }
  std::move(callback).Run();
}

void ChromeOSSystemProfileProvider::OnArcFeaturesParsed(
    base::OnceClosure callback,
    std::optional<arc::ArcFeatures> features) {
  base::ScopedClosureRunner runner(std::move(callback));
  if (!features) {
    LOG(WARNING) << "ArcFeatures not available on this build";
    return;
  }
  arc_release_ = features->build_props.release_version;
}

void ChromeOSSystemProfileProvider::OnTpmManagerGetRwVersionInfo(
    base::OnceClosure callback,
    const tpm_manager::GetVersionInfoReply& reply) {
  if (reply.status() == tpm_manager::STATUS_SUCCESS) {
    tpm_rw_firmware_version_ = reply.rw_version();
  } else {
    LOG(ERROR) << "Failed to get TPM RW version info.";
  }
  std::move(callback).Run();
}

void ChromeOSSystemProfileProvider::SetCellularDeviceVariant(
    base::OnceClosure callback,
    std::string cellular_device_variant) {
  cellular_device_variant_ = cellular_device_variant;
  std::move(callback).Run();
}