#include "chromecast/metrics/cast_metrics_service_client.h"
#include <string>
#include <string_view>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chromecast/base/cast_sys_info_util.h"
#include "chromecast/base/chromecast_switches.h"
#include "chromecast/base/path_utils.h"
#include "chromecast/base/pref_names.h"
#include "chromecast/base/version.h"
#include "components/metrics/client_info.h"
#include "components/metrics/enabled_state_provider.h"
#include "components/metrics/metrics_log_uploader.h"
#include "components/metrics/metrics_provider.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/net/net_metrics_log_uploader.h"
#include "components/metrics/persistent_synthetic_trial_observer.h"
#include "components/metrics/server_urls.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if BUILDFLAG(IS_ANDROID)
#include "chromecast/base/android/dumpstate_writer.h"
#endif
namespace chromecast {
namespace metrics {
namespace {
const int kStandardUploadIntervalMinutes = 5;
const char kMetricsOldClientID[] = "user_experience_metrics.client_id";
#if BUILDFLAG(IS_ANDROID)
const char kClientIdName[] = "Client ID";
#endif
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA)
const struct ChannelMap {
const char* chromecast_channel;
const ::metrics::SystemProfileProto::Channel chrome_channel;
} kMetricsChannelMap[] = {
{"canary-channel", ::metrics::SystemProfileProto::CHANNEL_CANARY},
{"dev-channel", ::metrics::SystemProfileProto::CHANNEL_DEV},
{"developer-channel", ::metrics::SystemProfileProto::CHANNEL_DEV},
{"beta-channel", ::metrics::SystemProfileProto::CHANNEL_BETA},
{"dogfood-channel", ::metrics::SystemProfileProto::CHANNEL_BETA},
{"stable-channel", ::metrics::SystemProfileProto::CHANNEL_STABLE},
};
::metrics::SystemProfileProto::Channel GetReleaseChannelFromUpdateChannelName(
const std::string& channel_name) {
if (channel_name.empty())
return ::metrics::SystemProfileProto::CHANNEL_UNKNOWN;
for (const auto& channel_map : kMetricsChannelMap) {
if (channel_name.compare(channel_map.chromecast_channel) == 0)
return channel_map.chrome_channel;
}
return ::metrics::SystemProfileProto::CHANNEL_BETA;
}
#endif
}
void CastMetricsServiceClient::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(kMetricsOldClientID, std::string());
}
variations::SyntheticTrialRegistry*
CastMetricsServiceClient::GetSyntheticTrialRegistry() {
return synthetic_trial_registry_.get();
}
::metrics::MetricsService* CastMetricsServiceClient::GetMetricsService() {
return metrics_service_.get();
}
void CastMetricsServiceClient::SetMetricsClientId(
const std::string& client_id) {
client_id_ = client_id;
LOG(INFO) << "Metrics client ID set: " << client_id;
if (delegate_)
delegate_->SetMetricsClientId(client_id);
#if BUILDFLAG(IS_ANDROID)
DumpstateWriter::AddDumpValue(kClientIdName, client_id);
#endif
}
void CastMetricsServiceClient::StoreClientInfo(
const ::metrics::ClientInfo& client_info) {
}
std::unique_ptr<::metrics::ClientInfo>
CastMetricsServiceClient::LoadClientInfo() {
std::unique_ptr<::metrics::ClientInfo> client_info(new ::metrics::ClientInfo);
client_info_loaded_ = true;
if (!pref_service_->GetBoolean(prefs::kMetricsIsNewClientID)) {
if (!pref_service_->GetString(kMetricsOldClientID).empty()) {
client_info->client_id =
base::Uuid::GenerateRandomV4().AsLowercaseString();
pref_service_->SetBoolean(prefs::kMetricsIsNewClientID, true);
return client_info;
}
}
if (!force_client_id_.empty() &&
base::Uuid::ParseCaseInsensitive(force_client_id_).is_valid()) {
client_info->client_id = force_client_id_;
return client_info;
}
if (force_client_id_.empty()) {
LOG(WARNING) << "Empty client id from platform,"
<< " assuming this is the first boot up of a new device.";
} else {
LOG(ERROR) << "Invalid client id from platform: " << force_client_id_
<< " from platform.";
}
return nullptr;
}
int32_t CastMetricsServiceClient::GetProduct() {
return ::metrics::ChromeUserMetricsExtension::CAST;
}
std::string CastMetricsServiceClient::GetApplicationLocale() {
return base::i18n::GetConfiguredLocale();
}
const network_time::NetworkTimeTracker*
CastMetricsServiceClient::GetNetworkTimeTracker() {
return nullptr;
}
bool CastMetricsServiceClient::GetBrand(std::string* brand_code) {
return false;
}
::metrics::SystemProfileProto::Channel CastMetricsServiceClient::GetChannel() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
switch (cast_sys_info_->GetBuildType()) {
case CastSysInfo::BUILD_ENG:
return ::metrics::SystemProfileProto::CHANNEL_UNKNOWN;
case CastSysInfo::BUILD_BETA:
return ::metrics::SystemProfileProto::CHANNEL_BETA;
case CastSysInfo::BUILD_PRODUCTION:
return ::metrics::SystemProfileProto::CHANNEL_STABLE;
}
NOTREACHED();
#else
return GetReleaseChannelFromUpdateChannelName(
cast_sys_info_->GetSystemReleaseChannel());
#endif
}
bool CastMetricsServiceClient::IsExtendedStableChannel() {
return false;
}
std::string CastMetricsServiceClient::GetVersionString() {
int build_number;
if (!base::StringToInt(CAST_BUILD_INCREMENTAL, &build_number))
build_number = 0;
std::string version_string(PRODUCT_VERSION);
version_string.append("-K");
version_string.append(base::NumberToString(build_number));
const ::metrics::SystemProfileProto::Channel channel = GetChannel();
CHECK(!CAST_IS_DEBUG_BUILD() ||
channel != ::metrics::SystemProfileProto::CHANNEL_STABLE);
const bool is_official_build =
build_number > 0 && !CAST_IS_DEBUG_BUILD() &&
channel != ::metrics::SystemProfileProto::CHANNEL_UNKNOWN;
if (!is_official_build)
version_string.append("-devel");
return version_string;
}
void CastMetricsServiceClient::CollectFinalMetricsForLog(
base::OnceClosure done_callback) {
if (collect_final_metrics_cb_)
collect_final_metrics_cb_.Run(std::move(done_callback));
else
std::move(done_callback).Run();
}
void CastMetricsServiceClient::SetCallbacks(
base::RepeatingCallback<void(base::OnceClosure)> collect_final_metrics_cb,
base::RepeatingCallback<void(base::OnceClosure)> external_events_cb) {
collect_final_metrics_cb_ = collect_final_metrics_cb;
external_events_cb_ = external_events_cb;
}
GURL CastMetricsServiceClient::GetMetricsServerUrl() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kOverrideMetricsUploadUrl)) {
return GURL(
command_line->GetSwitchValueASCII(switches::kOverrideMetricsUploadUrl));
}
return ::metrics::GetCastMetricsServerUrl();
}
std::unique_ptr<::metrics::MetricsLogUploader>
CastMetricsServiceClient::CreateUploader(
const GURL& server_url,
const GURL& insecure_server_url,
std::string_view mime_type,
::metrics::MetricsLogUploader::MetricServiceType service_type,
const ::metrics::MetricsLogUploader::UploadCallback& on_upload_complete) {
return std::make_unique<::metrics::NetMetricsLogUploader>(
url_loader_factory_, server_url, insecure_server_url, mime_type,
service_type, on_upload_complete);
}
base::TimeDelta CastMetricsServiceClient::GetStandardUploadInterval() {
return base::Minutes(kStandardUploadIntervalMinutes);
}
::metrics::MetricsLogStore::StorageLimits
CastMetricsServiceClient::GetStorageLimits() const {
auto limits = ::metrics::MetricsServiceClient::GetStorageLimits();
if (delegate_)
delegate_->ApplyMetricsStorageLimits(&limits);
return limits;
}
bool CastMetricsServiceClient::IsConsentGiven() const {
return pref_service_->GetBoolean(prefs::kOptInStats);
}
bool CastMetricsServiceClient::IsReportingEnabled() const {
return pref_service_->GetBoolean(prefs::kTosAccepted) &&
::metrics::EnabledStateProvider::IsReportingEnabled();
}
void CastMetricsServiceClient::UpdateMetricsServiceState() {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CastMetricsServiceClient::UpdateMetricsServiceState,
base::Unretained(this)));
return;
}
if (IsConsentGiven()) {
metrics_service_->Start();
if (!IsReportingEnabled()) {
metrics_service_->DisableReporting();
}
} else {
metrics_service_->Stop();
}
}
void CastMetricsServiceClient::DisableMetricsService() {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CastMetricsServiceClient::DisableMetricsService,
base::Unretained(this)));
return;
}
metrics_service_->Stop();
}
CastMetricsServiceClient::CastMetricsServiceClient(
CastMetricsServiceDelegate* delegate,
PrefService* pref_service,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: delegate_(delegate),
pref_service_(pref_service),
client_info_loaded_(false),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
url_loader_factory_(url_loader_factory),
cast_sys_info_(CreateSysInfo()) {}
CastMetricsServiceClient::~CastMetricsServiceClient() = default;
void CastMetricsServiceClient::OnApplicationNotIdle() {
metrics_service_->OnApplicationNotIdle();
}
void CastMetricsServiceClient::SetForceClientId(const std::string& client_id) {
DCHECK(force_client_id_.empty());
DCHECK(!client_info_loaded_)
<< "Force client ID must be set before client info is loaded.";
force_client_id_ = client_id;
SetMetricsClientId(force_client_id_);
}
void CastMetricsServiceClient::InitializeMetricsService() {
DCHECK(!metrics_state_manager_);
metrics_state_manager_ = ::metrics::MetricsStateManager::Create(
pref_service_, this, std::wstring(),
base::FilePath(),
::metrics::StartupVisibility::kUnknown, ::metrics::EntropyParams{},
base::BindRepeating(&CastMetricsServiceClient::StoreClientInfo,
base::Unretained(this)),
base::BindRepeating(&CastMetricsServiceClient::LoadClientInfo,
base::Unretained(this)));
DCHECK(base::FieldTrialList::GetInstance());
metrics_state_manager_->InstantiateFieldTrialList();
synthetic_trial_registry_ =
std::make_unique<variations::SyntheticTrialRegistry>();
synthetic_trial_observation_.Observe(synthetic_trial_registry_.get());
metrics_service_.reset(new ::metrics::MetricsService(
metrics_state_manager_.get(), this, pref_service_));
metrics_state_manager_->ForceClientIdCreation();
SetMetricsClientId(metrics_state_manager_->client_id());
}
void CastMetricsServiceClient::StartMetricsService() {
if (delegate_)
delegate_->RegisterMetricsProviders(metrics_service_.get());
metrics_service_->InitializeMetricsRecordingState();
#if !BUILDFLAG(IS_ANDROID)
metrics_state_manager_->LogHasSessionShutdownCleanly(false);
#endif
UpdateMetricsServiceState();
}
void CastMetricsServiceClient::Finalize() {
metrics_service_->Stop();
}
void CastMetricsServiceClient::ProcessExternalEvents(base::OnceClosure cb) {
if (external_events_cb_)
external_events_cb_.Run(std::move(cb));
else
std::move(cb).Run();
}
}
}