// 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/apps/almanac_api_client/device_info_manager.h"
#include <optional>
#include <string_view>
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/apps/almanac_api_client/proto/client_context.pb.h"
#include "chrome/browser/apps/user_type_filter.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/borealis/borealis_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/channel_info.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/version/version_loader.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
namespace {
apps::proto::ClientDeviceContext::Channel ConvertChannelTypeToProto(
const version_info::Channel channel) {
switch (channel) {
case version_info::Channel::CANARY:
return apps::proto::ClientDeviceContext::CHANNEL_CANARY;
case version_info::Channel::DEV:
return apps::proto::ClientDeviceContext::CHANNEL_DEV;
case version_info::Channel::BETA:
return apps::proto::ClientDeviceContext::CHANNEL_BETA;
case version_info::Channel::STABLE:
return apps::proto::ClientDeviceContext::CHANNEL_STABLE;
case version_info::Channel::UNKNOWN:
// The default channel is used for builds without a channel (e.g.
// local builds).
return apps::proto::ClientDeviceContext::CHANNEL_DEFAULT;
}
}
apps::proto::ClientUserContext::UserType ConvertStringUserTypeToProto(
const std::string& user_type) {
if (user_type == apps::kUserTypeUnmanaged) {
return apps::proto::ClientUserContext::USERTYPE_UNMANAGED;
} else if (user_type == apps::kUserTypeManaged) {
return apps::proto::ClientUserContext::USERTYPE_MANAGED;
} else if (user_type == apps::kUserTypeChild) {
return apps::proto::ClientUserContext::USERTYPE_CHILD;
} else if (user_type == apps::kUserTypeGuest) {
return apps::proto::ClientUserContext::USERTYPE_GUEST;
} else if (user_type == apps::kUserTypeManagedGuest) {
return apps::proto::ClientUserContext::USERTYPE_MANAGED_GUEST;
}
return apps::proto::ClientUserContext::USERTYPE_UNKNOWN;
}
// Adds version numbers and custom label tag to `info`, returning the updated
// object. Called on a background thread, since loading these values may block.
apps::DeviceInfo LoadVersionAndCustomLabel(apps::DeviceInfo info,
bool arc_enabled) {
info.version_info.ash_chrome = version_info::GetVersionNumber();
std::optional<std::string> platform_version =
chromeos::version_loader::GetVersion(
chromeos::version_loader::VERSION_SHORT);
info.version_info.platform = platform_version.value_or("");
info.version_info.channel = chrome::GetChannel();
// Only set arc_sdk if it is enabled.
if (arc_enabled) {
base::StringToInt(
chromeos::version_loader::GetArcAndroidSdkVersion().value_or(
std::string()),
&info.version_info.arc_sdk);
}
// Load device identifiers from chromeos-config, as per
// https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/chromeos-config/README.md#identity.
constexpr char kCustomLabelFileName[] =
"/run/chromeos-config/v1/identity/custom-label-tag";
constexpr char kCustomizationIdFileName[] =
"/run/chromeos-config/v1/identity/customization-id";
// Attempt to load the custom-label tag from kCustomLabelFileName first. Older
// devices may instead have a "customization id" stored at
// kCustomizationIdFileName. If neither file exists, the device has no
// custom-label tag, and `info.custom_label_tag` should remain unset.
std::string custom_label;
if (base::ReadFileToString(base::FilePath(kCustomLabelFileName),
&custom_label)) {
info.custom_label_tag = custom_label;
} else if (base::ReadFileToString(base::FilePath(kCustomizationIdFileName),
&custom_label)) {
info.custom_label_tag = custom_label;
}
return info;
}
} // namespace
namespace apps {
VersionInfo::VersionInfo() = default;
VersionInfo::VersionInfo(const VersionInfo& other) = default;
VersionInfo& VersionInfo::operator=(const VersionInfo& other) = default;
VersionInfo::~VersionInfo() = default;
DeviceInfo::DeviceInfo() = default;
DeviceInfo::DeviceInfo(const DeviceInfo& other) = default;
DeviceInfo& DeviceInfo::operator=(const DeviceInfo& other) = default;
DeviceInfo::~DeviceInfo() = default;
proto::ClientDeviceContext DeviceInfo::ToDeviceContext() const {
proto::ClientDeviceContext device_context;
device_context.set_board(board);
device_context.set_model(model);
device_context.set_channel(ConvertChannelTypeToProto(version_info.channel));
device_context.mutable_versions()->set_chrome_ash(version_info.ash_chrome);
device_context.mutable_versions()->set_chrome_os_platform(
version_info.platform);
device_context.mutable_versions()->set_arc_sdk(version_info.arc_sdk);
device_context.mutable_versions()->set_steam_client(
version_info.steam_client);
device_context.set_hardware_id(hardware_id);
if (custom_label_tag.has_value()) {
device_context.set_custom_label_tag(custom_label_tag.value());
}
return device_context;
}
proto::ClientUserContext DeviceInfo::ToUserContext() const {
proto::ClientUserContext user_context;
user_context.set_language(locale);
user_context.set_user_type(ConvertStringUserTypeToProto(user_type));
return user_context;
}
DeviceInfoManager::DeviceInfoManager(Profile* profile) : profile_(profile) {}
DeviceInfoManager::~DeviceInfoManager() = default;
// This method populates:
// - board
// - user_type
// - hardware_id
// The method then asynchronously populates:
// - version_info and custom_label_tag (LoadVersionAndCustomLabel)
// - model (OnModelInfo)
void DeviceInfoManager::GetDeviceInfo(
base::OnceCallback<void(DeviceInfo)> callback) {
if (cached_info_) {
std::move(callback).Run(*cached_info_);
return;
}
bool is_first_get = pending_callbacks_.empty();
pending_callbacks_.push_back(std::move(callback));
if (!is_first_get) {
return;
}
// Start loading the Device Info so that we can cache it and deliver to
// pending callbacks.
DeviceInfo device_info;
device_info.board = base::ToLowerASCII(base::SysInfo::HardwareModelName());
device_info.user_type = apps::DetermineUserType(profile_);
ash::system::StatisticsProvider* provider =
ash::system::StatisticsProvider::GetInstance();
std::optional<std::string_view> hwid =
provider->GetMachineStatistic(ash::system::kHardwareClassKey);
device_info.hardware_id = std::string(hwid.value_or(""));
// Set steam_client to "TRUE" to indicate it is enabled.
if (borealis::BorealisFeatures(profile_).IsEnabled()) {
device_info.version_info.steam_client = "TRUE";
}
// Locale
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
device_info.locale = prefs->GetString(language::prefs::kApplicationLocale);
// If there's no stored locale preference, fall back to the current UI
// language.
if (device_info.locale.empty()) {
device_info.locale = g_browser_process->GetApplicationLocale();
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&LoadVersionAndCustomLabel, std::move(device_info),
arc::IsArcPlayStoreEnabledForProfile(profile_)),
base::BindOnce(&DeviceInfoManager::OnLoadedVersionAndCustomLabel,
weak_ptr_factory_.GetWeakPtr()));
}
void DeviceInfoManager::OnLoadedVersionAndCustomLabel(DeviceInfo device_info) {
base::SysInfo::GetHardwareInfo(base::BindOnce(&DeviceInfoManager::OnModelInfo,
weak_ptr_factory_.GetWeakPtr(),
std::move(device_info)));
}
void DeviceInfoManager::OnModelInfo(DeviceInfo device_info,
base::SysInfo::HardwareInfo hardware_info) {
device_info.model = hardware_info.model;
cached_info_ = std::move(device_info);
for (auto& callback : std::exchange(pending_callbacks_, {})) {
std::move(callback).Run(*cached_info_);
}
}
std::ostream& operator<<(std::ostream& os, const DeviceInfo& device_info) {
os << "Device Info: " << std::endl;
os << "- Board: " << device_info.board << std::endl;
os << "- Model: " << device_info.model << std::endl;
os << "- Hardware ID: " << device_info.hardware_id << std::endl;
os << "- Custom Label: " << device_info.custom_label_tag.value_or("(empty)")
<< std::endl;
os << "- User Type: " << device_info.user_type << std::endl;
os << "- Locale: " << device_info.locale << std::endl;
os << device_info.version_info;
return os;
}
std::ostream& operator<<(std::ostream& os, const VersionInfo& version_info) {
os << "- Version Info: " << std::endl;
os << " - Ash Chrome: " << version_info.ash_chrome << std::endl;
os << " - Platform: " << version_info.platform << std::endl;
os << " - ARC SDK: " << version_info.arc_sdk << std::endl;
os << " - Steam client: " << version_info.steam_client << std::endl;
os << " - Channel: " << version_info::GetChannelString(version_info.channel)
<< std::endl;
return os;
}
} // namespace apps