chromium/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.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/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h"

#include <cstddef>
#include <memory>
#include <optional>
#include <string_view>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list_types.h"
#include "base/sequence_checker.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/app_service_proxy_ash.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
#include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_metric_reporting_utils.h"
#include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/reporting/metrics/metric_event_observer.h"
#include "components/reporting/metrics/reporting_settings.h"
#include "components/reporting/proto/synced/metric_data.pb.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/features.h"
#include "components/services/app_service/public/protos/app_types.pb.h"

namespace reporting {
namespace {

// Derives disk space consumption for the specified list, assuming the size of
// each individual entity in the list is the same.
size_t GetDiskConsumptionForList(const base::Value::List& list) {
  if (list.empty()) {
    return 0;
  }
  return list.size() * sizeof(list.front());
}
}  // namespace

// static
std::unique_ptr<AppEventsObserver> AppEventsObserver::CreateForProfile(
    Profile* profile,
    const ReportingSettings* reporting_settings) {
  CHECK(profile);
  const auto profile_weak_ptr = profile->GetWeakPtr();
  auto app_platform_metrics_retriever =
      std::make_unique<AppPlatformMetricsRetriever>(profile_weak_ptr);
  return base::WrapUnique(new AppEventsObserver(
      profile_weak_ptr, std::move(app_platform_metrics_retriever),
      reporting_settings));
}

// static
std::unique_ptr<AppEventsObserver> AppEventsObserver::CreateForTest(
    Profile* profile,
    std::unique_ptr<AppPlatformMetricsRetriever> app_platform_metrics_retriever,
    const ReportingSettings* reporting_settings) {
  CHECK(profile);
  return base::WrapUnique(new AppEventsObserver(
      profile->GetWeakPtr(), std::move(app_platform_metrics_retriever),
      reporting_settings));
}

AppEventsObserver::AppInstallTracker::AppInstallTracker(
    base::WeakPtr<Profile> profile)
    : profile_(profile) {}

AppEventsObserver::AppInstallTracker::~AppInstallTracker() = default;

void AppEventsObserver::AppInstallTracker::Add(std::string_view app_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!profile_) {
    // Profile destroyed. Skip.
    return;
  }
  CHECK(!Contains(app_id)) << "App already being tracked";
  ScopedListPrefUpdate apps_installed_pref(profile_->GetPrefs(),
                                           ::ash::reporting::kAppsInstalled);
  apps_installed_pref->Append(app_id);
  base::UmaHistogramCounts1M(
      kDiskConsumptionMetricsName,
      GetDiskConsumptionForList(apps_installed_pref.Get()));
}

void AppEventsObserver::AppInstallTracker::Remove(std::string_view app_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!profile_) {
    // Profile destroyed. Skip.
    return;
  }
  CHECK(Contains(app_id)) << "App not being tracked";
  ScopedListPrefUpdate apps_installed_pref(profile_->GetPrefs(),
                                           ::ash::reporting::kAppsInstalled);
  apps_installed_pref->EraseValue(base::Value(app_id));
  base::UmaHistogramCounts1M(
      kDiskConsumptionMetricsName,
      GetDiskConsumptionForList(apps_installed_pref.Get()));
}

bool AppEventsObserver::AppInstallTracker::Contains(
    std::string_view app_id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(profile_);
  return base::Contains(
      profile_->GetPrefs()->GetList(::ash::reporting::kAppsInstalled), app_id);
}

AppEventsObserver::AppEventsObserver(
    base::WeakPtr<Profile> profile,
    std::unique_ptr<AppPlatformMetricsRetriever> app_platform_metrics_retriever,
    const ReportingSettings* reporting_settings)
    : profile_(profile),
      app_platform_metrics_retriever_(
          std::move(app_platform_metrics_retriever)),
      reporting_settings_(reporting_settings) {
  app_install_tracker_ = std::make_unique<AppInstallTracker>(profile);

  CHECK(app_platform_metrics_retriever_);
  app_platform_metrics_retriever_->GetAppPlatformMetrics(base::BindOnce(
      &AppEventsObserver::InitEventObserver, weak_ptr_factory_.GetWeakPtr()));
}

AppEventsObserver::~AppEventsObserver() = default;

void AppEventsObserver::SetOnEventObservedCallback(
    MetricRepeatingCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  on_metric_observed_ = std::move(callback);
}

void AppEventsObserver::SetReportingEnabled(bool is_enabled) {
  // Do nothing. We retrieve the reporting setting and validate the app type is
  // allowed before we report observed events.
}

void AppEventsObserver::InitEventObserver(
    ::apps::AppPlatformMetrics* app_platform_metrics) {
  // Runs on the same sequence as the `AppPlatformMetricsRetriever` because they
  // both use the UI thread.
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!app_platform_metrics) {
    // This can happen if the `AppPlatformMetrics` component initialization
    // failed (for example, component was destructed). We just abort
    // initialization of the event observer when this happens.
    return;
  }
  observer_.Observe(app_platform_metrics);
}

void AppEventsObserver::OnAppInstalled(const std::string& app_id,
                                       ::apps::AppType app_type,
                                       ::apps::InstallSource app_install_source,
                                       ::apps::InstallReason app_install_reason,
                                       ::apps::InstallTime app_install_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(reporting_settings_);
  if (!profile_) {
    // Either the profile was destroyed. Skip.
    return;
  }

  // The app was already installed (likely in a prior session). Skip
  if (app_install_tracker_->Contains(app_id)) {
    return;
  }

  // Track app install to prevent future install event reports.
  app_install_tracker_->Add(app_id);

  if (!::ash::reporting::IsAppTypeAllowed(
          app_type, reporting_settings_.get(),
          ::ash::reporting::kReportAppInventory)) {
    return;
  }

  MetricData metric_data;
  metric_data.mutable_event_data()->set_type(MetricEventType::APP_INSTALLED);

  auto* const app_install_data = metric_data.mutable_telemetry_data()
                                     ->mutable_app_telemetry()
                                     ->mutable_app_install_data();
  auto public_app_id = app_id;
  if (const std::optional<std::string> app_publisher_id =
          GetPublisherIdForApp(app_id, profile_.get());
      app_publisher_id.has_value()) {
    public_app_id = app_publisher_id.value();
  }
  app_install_data->set_app_id(public_app_id);
  app_install_data->set_app_type(
      ::apps::ConvertAppTypeToProtoApplicationType(app_type));
  app_install_data->set_app_install_source(
      ::apps::ConvertInstallSourceToProtoApplicationInstallSource(
          app_install_source));
  app_install_data->set_app_install_reason(
      ::apps::ConvertInstallReasonToProtoApplicationInstallReason(
          app_install_reason));
  app_install_data->set_app_install_time(
      ::apps::ConvertInstallTimeToProtoApplicationInstallTime(
          app_install_time));

  on_metric_observed_.Run(std::move(metric_data));
}

void AppEventsObserver::OnAppLaunched(const std::string& app_id,
                                      ::apps::AppType app_type,
                                      ::apps::LaunchSource app_launch_source) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(reporting_settings_);
  if (!profile_ || !::ash::reporting::IsAppTypeAllowed(
                       app_type, reporting_settings_.get(),
                       ::ash::reporting::kReportAppInventory)) {
    return;
  }

  MetricData metric_data;
  metric_data.mutable_event_data()->set_type(MetricEventType::APP_LAUNCHED);

  auto* const app_launch_data = metric_data.mutable_telemetry_data()
                                    ->mutable_app_telemetry()
                                    ->mutable_app_launch_data();
  auto public_app_id = app_id;
  if (const std::optional<std::string> app_publisher_id =
          GetPublisherIdForApp(app_id, profile_.get());
      app_publisher_id.has_value()) {
    public_app_id = app_publisher_id.value();
  }
  app_launch_data->set_app_id(public_app_id);
  app_launch_data->set_app_type(
      ::apps::ConvertAppTypeToProtoApplicationType(app_type));
  app_launch_data->set_app_launch_source(
      ::apps::ConvertLaunchSourceToProtoApplicationLaunchSource(
          app_launch_source));

  on_metric_observed_.Run(std::move(metric_data));
}

void AppEventsObserver::OnAppUninstalled(
    const std::string& app_id,
    ::apps::AppType app_type,
    ::apps::UninstallSource app_uninstall_source) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(reporting_settings_);
  if (!profile_) {
    // Profile destroyed. Return.
    return;
  }

  if (app_install_tracker_ && app_install_tracker_->Contains(app_id)) {
    // Stop tracking app install if it is being tracked.
    app_install_tracker_->Remove(app_id);
  }
  if (!::ash::reporting::IsAppTypeAllowed(
          app_type, reporting_settings_.get(),
          ::ash::reporting::kReportAppInventory)) {
    return;
  }

  MetricData metric_data;
  metric_data.mutable_event_data()->set_type(MetricEventType::APP_UNINSTALLED);

  auto* const app_uninstall_data = metric_data.mutable_telemetry_data()
                                       ->mutable_app_telemetry()
                                       ->mutable_app_uninstall_data();
  auto public_app_id = app_id;
  if (const std::optional<std::string> app_publisher_id =
          GetPublisherIdForApp(app_id, profile_.get());
      app_publisher_id.has_value()) {
    public_app_id = app_publisher_id.value();
  }
  app_uninstall_data->set_app_id(public_app_id);
  app_uninstall_data->set_app_type(
      ::apps::ConvertAppTypeToProtoApplicationType(app_type));
  app_uninstall_data->set_app_uninstall_source(
      ::apps::ConvertUninstallSourceToProtoApplicationUninstallSource(
          app_uninstall_source));

  on_metric_observed_.Run(std::move(metric_data));
}

void AppEventsObserver::OnAppPlatformMetricsDestroyed() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_.Reset();
}

}  // namespace reporting