chromium/chrome/browser/ash/policy/reporting/arc_app_install_event_logger.cc

// Copyright 2018 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/arc_app_install_event_logger.h"

#include <stdint.h>

#include <algorithm>
#include <iterator>

#include "ash/components/arc/arc_prefs.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/policy/arc_policy_util.h"
#include "chrome/browser/ash/policy/reporting/install_event_logger_base.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"

namespace policy {

namespace {

namespace em = ::enterprise_management;

constexpr int kNonComplianceReasonAppNotInstalled = 5;

std::set<std::string> GetRequestedPackagesFromPolicy(const PolicyMap& policy) {
  const base::Value* const arc_enabled =
      policy.GetValue(key::kArcEnabled, base::Value::Type::BOOLEAN);
  if (!arc_enabled || !arc_enabled->GetBool())
    return {};

  const base::Value* const arc_policy =
      policy.GetValue(key::kArcPolicy, base::Value::Type::STRING);
  if (!arc_policy)
    return {};

  return arc::policy_util::GetRequestedPackagesFromArcPolicy(
      arc_policy->GetString());
}

}  // namespace

ArcAppInstallEventLogger::ArcAppInstallEventLogger(Delegate* delegate,
                                                   Profile* profile)
    : InstallEventLoggerBase(profile), delegate_(delegate) {
  if (!arc::IsArcAllowedForProfile(profile_)) {
    AddForSetOfApps(GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
                    CreateEvent(em::AppInstallReportLogEvent::CANCELED));
    Clear(profile_);
    return;
  }

  PolicyService* const policy_service =
      profile_->GetProfilePolicyConnector()->policy_service();
  EvaluatePolicy(policy_service->GetPolicies(
                     PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())),
                 true /* initial */);

  observing_ = true;
  arc::ArcPolicyBridge* bridge =
      arc::ArcPolicyBridge::GetForBrowserContext(profile_);
  bridge->AddObserver(this);
  policy_service->AddObserver(POLICY_DOMAIN_CHROME, this);
}

ArcAppInstallEventLogger::~ArcAppInstallEventLogger() {
  if (log_collector_) {
    log_collector_->OnLogout();
  }
  if (observing_) {
    arc::ArcPolicyBridge::GetForBrowserContext(profile_)->RemoveObserver(this);
    profile_->GetProfilePolicyConnector()->policy_service()->RemoveObserver(
        POLICY_DOMAIN_CHROME, this);
  }
}

// static
void ArcAppInstallEventLogger::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterListPref(arc::prefs::kArcPushInstallAppsRequested);
  registry->RegisterListPref(arc::prefs::kArcPushInstallAppsPending);
}

// static
void ArcAppInstallEventLogger::Clear(Profile* profile) {
  profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsRequested);
  profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsPending);
}

void ArcAppInstallEventLogger::AddForAllPackages(
    std::unique_ptr<em::AppInstallReportLogEvent> event) {
  EnsureTimestampSet(event.get());
  AddForSetOfApps(GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
                  std::move(event));
}

void ArcAppInstallEventLogger::Add(
    const std::string& package,
    bool gather_disk_space_info,
    std::unique_ptr<em::AppInstallReportLogEvent> event) {
  AddEvent(package, gather_disk_space_info, event);
}

void ArcAppInstallEventLogger::UpdatePolicySuccessRate(
    const std::string& package,
    bool success) {
  policy_data_helper_.UpdatePolicySuccessRate(package, success);
}

void ArcAppInstallEventLogger::OnPolicyUpdated(const PolicyNamespace& ns,
                                               const PolicyMap& previous,
                                               const PolicyMap& current) {
  EvaluatePolicy(current, false /* initial */);
}

void ArcAppInstallEventLogger::OnPolicySent(const std::string& policy) {
  requested_in_arc_ =
      arc::policy_util::GetRequestedPackagesFromArcPolicy(policy);
}

void ArcAppInstallEventLogger::OnComplianceReportReceived(
    const base::Value* compliance_report) {
  const base::Value::List* const details =
      compliance_report->GetDict().FindList("nonComplianceDetails");
  if (!details) {
    return;
  }

  const std::set<std::string> all_force_install_apps_in_policy =
      GetPackagesFromPref(arc::prefs::kArcPushInstallAppsRequested);
  const std::set<std::string> previous_pending =
      GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);

  std::set<std::string> noncompliant_apps_in_report;
  for (const auto& detail : *details) {
    const base::Value::Dict& details_dict = detail.GetDict();
    const std::optional<int> reason =
        details_dict.FindInt("nonComplianceReason");
    if (!reason || *reason != kNonComplianceReasonAppNotInstalled) {
      continue;
    }
    const std::string* const app_name = details_dict.FindString("packageName");
    if (!app_name || app_name->empty()) {
      continue;
    }
    noncompliant_apps_in_report.insert(*app_name);
  }
  const std::set<std::string> all_installed_apps = GetDifference(
      all_force_install_apps_in_policy, noncompliant_apps_in_report);

  std::set<std::string> newly_installed_apps;
  std::set_intersection(
      previous_pending.begin(), previous_pending.end(),
      all_installed_apps.begin(), all_installed_apps.end(),
      std::inserter(newly_installed_apps, newly_installed_apps.end()));

  AddForSetOfAppsWithDiskSpaceInfo(
      newly_installed_apps, CreateEvent(em::AppInstallReportLogEvent::SUCCESS));

  if (newly_installed_apps.empty()) {
    return;
  }

  SetPref(arc::prefs::kArcPushInstallAppsPending, noncompliant_apps_in_report);

  if (!noncompliant_apps_in_report.empty()) {
    UpdateCollector(noncompliant_apps_in_report);
  } else {
    StopCollector();
  }
}

std::set<std::string> ArcAppInstallEventLogger::GetPackagesFromPref(
    const std::string& pref_name) const {
  std::set<std::string> packages;
  for (const auto& package : profile_->GetPrefs()->GetList(pref_name)) {
    if (!package.is_string()) {
      continue;
    }
    packages.insert(package.GetString());
  }
  return packages;
}

void ArcAppInstallEventLogger::SetPref(const std::string& pref_name,
                                       const std::set<std::string>& packages) {
  base::Value::List value;
  for (const std::string& package : packages) {
    value.Append(package);
  }
  profile_->GetPrefs()->SetList(pref_name, std::move(value));
}

void ArcAppInstallEventLogger::UpdateCollector(
    const std::set<std::string>& pending) {
  if (!log_collector_) {
    log_collector_ = std::make_unique<ArcAppInstallEventLogCollector>(
        this, profile_, pending);
  } else {
    log_collector_->OnPendingPackagesChanged(pending);
  }
}

void ArcAppInstallEventLogger::StopCollector() {
  log_collector_.reset();
}

void ArcAppInstallEventLogger::EvaluatePolicy(const PolicyMap& policy,
                                              bool initial) {
  const std::set<std::string> previous_requested =
      GetPackagesFromPref(arc::prefs::kArcPushInstallAppsRequested);
  const std::set<std::string> previous_pending =
      GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);

  const std::set<std::string> current_requested =
      GetRequestedPackagesFromPolicy(policy);

  const std::set<std::string> added =
      GetDifference(current_requested, previous_requested);
  const std::set<std::string> removed =
      GetDifference(previous_pending, current_requested);
  AddForSetOfAppsWithDiskSpaceInfo(
      added, CreateEvent(em::AppInstallReportLogEvent::SERVER_REQUEST));
  AddForSetOfApps(removed, CreateEvent(em::AppInstallReportLogEvent::CANCELED));
  // Consider canceled packages as successful since they are not needed
  policy_data_helper_.UpdatePolicySuccessRateForPackages(removed,
                                                         /* success */ true);

  const std::set<std::string> previously_installed =
      GetDifference(previous_requested, previous_pending);
  const std::set<std::string> current_pending =
      GetDifference(current_requested, previously_installed);
  SetPref(arc::prefs::kArcPushInstallAppsRequested, current_requested);
  SetPref(arc::prefs::kArcPushInstallAppsPending, current_pending);

  policy_data_helper_.AddPolicyData(current_pending,
                                    previously_installed.size());

  if (!current_pending.empty()) {
    UpdateCollector(current_pending);
    if (initial) {
      log_collector_->OnLogin();
    }
  } else {
    StopCollector();
  }
}

void ArcAppInstallEventLogger::AddForSetOfApps(
    const std::set<std::string>& packages,
    std::unique_ptr<em::AppInstallReportLogEvent> event) {
  delegate_->GetAndroidId(
      base::BindOnce(&ArcAppInstallEventLogger::OnGetAndroidId,
                     weak_factory_.GetWeakPtr(), packages, std::move(event)));
}

void ArcAppInstallEventLogger::OnGetAndroidId(
    const std::set<std::string>& packages,
    std::unique_ptr<em::AppInstallReportLogEvent> event,
    bool ok,
    int64_t android_id) {
  if (ok) {
    event->set_android_id(android_id);
  }
  delegate_->Add(packages, *event);
}

}  // namespace policy