// Copyright 2023 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/chromeos/reporting/websites/website_usage_observer.h"
#include <memory>
#include <optional>
#include "base/functional/bind.h"
#include "base/json/values_util.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/metrics/website_metrics.h"
#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
#include "chrome/browser/chromeos/reporting/metric_reporting_prefs.h"
#include "chrome/browser/chromeos/reporting/websites/website_metrics_retriever_interface.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/reporting/metrics/reporting_settings.h"
#include "url/gurl.h"
namespace reporting {
WebsiteUsageObserver::WebsiteUsageObserver(
base::WeakPtr<Profile> profile,
const ReportingSettings* reporting_settings,
std::unique_ptr<WebsiteMetricsRetrieverInterface> website_metrics_retriever)
: profile_(profile),
reporting_settings_(reporting_settings),
website_metrics_retriever_(std::move(website_metrics_retriever)) {
CHECK(website_metrics_retriever_);
website_metrics_retriever_->GetWebsiteMetrics(
base::BindOnce(&WebsiteUsageObserver::InitUsageObserver,
weak_ptr_factory_.GetWeakPtr()));
}
WebsiteUsageObserver::~WebsiteUsageObserver() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void WebsiteUsageObserver::OnUrlUsage(const GURL& url,
base::TimeDelta running_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(reporting_settings_);
if (!profile_ || !IsWebsiteUsageTelemetryEnabled() ||
!IsWebsiteUrlAllowlisted(url, reporting_settings_,
kReportWebsiteTelemetryAllowlist)) {
return;
}
if (running_time < metrics::kMinimumWebsiteUsageTime) {
// Skip if there is no usage in millisecond granularity. Needed because we
// track website usage in milliseconds while `base::TimeDelta` internals use
// microsecond granularity.
return;
}
if (!profile_->GetPrefs()->HasPrefPath(kWebsiteUsage)) {
// No data in the pref store, so we create an empty dictionary for now.
profile_->GetPrefs()->SetDict(kWebsiteUsage, base::Value::Dict());
}
CreateOrUpdateWebsiteUsageEntry(url, running_time);
}
void WebsiteUsageObserver::InitUsageObserver(
::apps::WebsiteMetrics* website_metrics) {
if (!website_metrics) {
// This can happen if the `WebsiteMetrics` component initialization failed
// (for example, component was destructed). We just abort initialization of
// the usage observer when this happens.
return;
}
observer_.Observe(website_metrics);
}
void WebsiteUsageObserver::OnWebsiteMetricsDestroyed() {
observer_.Reset();
}
void WebsiteUsageObserver::CreateOrUpdateWebsiteUsageEntry(
const GURL& url,
const base::TimeDelta& running_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(profile_);
ScopedDictPrefUpdate usage_dict_pref(profile_->GetPrefs(), kWebsiteUsage);
const std::string& url_string = url.spec();
if (!usage_dict_pref->contains(url_string)) {
// Create a new entry in the pref store for given URL if one does not exist
// already.
usage_dict_pref->Set(url_string, base::TimeDeltaToValue(running_time));
return;
}
// Aggregate and update the running time otherwise.
const std::optional<const base::TimeDelta> saved_running_time_value =
base::ValueToTimeDelta(usage_dict_pref->Find(url_string));
if (saved_running_time_value.has_value()) {
usage_dict_pref->Set(
url_string, base::TimeDeltaToValue(saved_running_time_value.value() +
running_time));
}
}
bool WebsiteUsageObserver::IsWebsiteUsageTelemetryEnabled() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(reporting_settings_);
const base::Value::List* allowed_telemetry_types;
if (!reporting_settings_->GetList(kReportWebsiteTelemetry,
&allowed_telemetry_types)) {
// Policy likely unset. Disallow website usage telemetry tracking in any
// case.
return false;
}
// `allowed_telemetry_types` is not expected to change as we iterate and check
// for `usage` telemetry type below since this is triggered on the main
// (owning) sequence.
CHECK(allowed_telemetry_types);
auto it = std::find_if(
allowed_telemetry_types->begin(), allowed_telemetry_types->end(),
[](const base::Value& telemetry_type_value) {
return telemetry_type_value.GetString() == kWebsiteTelemetryUsageType;
});
const auto is_usage_telemetry_reporting_enabled =
(it != allowed_telemetry_types->end());
return is_usage_telemetry_reporting_enabled;
}
} // namespace reporting