blob: 1f96fc0f03a51aa8e8dedb1eee71bd6f5d9518dd [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/policy/status_collector/app_info_generator.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app_provider_factory.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/pref_names.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/session_manager/core/session_manager.h"
namespace em = enterprise_management;
namespace {
em::AppInfo::Status ExtractStatus(const apps::mojom::Readiness readiness) {
switch (readiness) {
case apps::mojom::Readiness::kReady:
return em::AppInfo::Status::AppInfo_Status_STATUS_INSTALLED;
case apps::mojom::Readiness::kRemoved:
case apps::mojom::Readiness::kUninstalledByUser:
case apps::mojom::Readiness::kUninstalledByMigration:
return em::AppInfo::Status::AppInfo_Status_STATUS_UNINSTALLED;
case apps::mojom::Readiness::kDisabledByBlocklist:
case apps::mojom::Readiness::kDisabledByPolicy:
case apps::mojom::Readiness::kDisabledByUser:
case apps::mojom::Readiness::kTerminated:
return em::AppInfo::Status::AppInfo_Status_STATUS_DISABLED;
case apps::mojom::Readiness::kUnknown:
return em::AppInfo::Status::AppInfo_Status_STATUS_UNKNOWN;
}
}
em::AppInfo::AppType ExtractAppType(const apps::mojom::AppType app_type) {
switch (app_type) {
case apps::mojom::AppType::kArc:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_ARC;
case apps::mojom::AppType::kBuiltIn:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_BUILTIN;
case apps::mojom::AppType::kCrostini:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_CROSTINI;
case apps::mojom::AppType::kPluginVm:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_PLUGINVM;
case apps::mojom::AppType::kExtension:
case apps::mojom::AppType::kStandaloneBrowserExtension:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_EXTENSION;
case apps::mojom::AppType::kWeb:
case apps::mojom::AppType::kSystemWeb:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_WEB;
case apps::mojom::AppType::kBorealis:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_BOREALIS;
case apps::mojom::AppType::kMacOs:
case apps::mojom::AppType::kStandaloneBrowser:
case apps::mojom::AppType::kRemote:
case apps::mojom::AppType::kUnknown:
return em::AppInfo::AppType::AppInfo_AppType_TYPE_UNKNOWN;
}
}
} // namespace
namespace policy {
AppInfoGenerator::AppInfoProvider::AppInfoProvider(Profile* profile)
: activity_storage(profile->GetPrefs(),
prefs::kAppActivityTimes,
/*day_start_offset=*/base::TimeDelta::FromSeconds(0)),
app_service_proxy(*apps::AppServiceProxyFactory::GetForProfile(profile)),
web_app_provider(
*web_app::WebAppProviderFactory::GetForProfile(profile)) {}
AppInfoGenerator::AppInfoProvider::~AppInfoProvider() = default;
AppInfoGenerator::AppInfoGenerator(
base::TimeDelta max_stored_past_activity_interval,
base::Clock* clock)
: max_stored_past_activity_interval_(max_stored_past_activity_interval),
clock_(*clock) {}
AppInfoGenerator::AppInstances::AppInstances(const base::Time start_time_)
: start_time(start_time_) {}
AppInfoGenerator::AppInstances::~AppInstances() = default;
AppInfoGenerator::~AppInfoGenerator() {
SetOpenDurationsToClosed(clock_.Now());
}
// static
void AppInfoGenerator::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kAppActivityTimes);
}
const AppInfoGenerator::Result AppInfoGenerator::Generate() const {
if (!should_report_) {
VLOG(1) << "App usage reporting is not enabled for this user.";
return absl::nullopt;
}
if (!provider_) {
VLOG(1) << "No affiliated user session. Returning empty app list.";
return absl::nullopt;
}
auto activity_periods = provider_->activity_storage.GetActivityPeriods();
auto activity_compare = [](const em::TimePeriod& time_period1,
const em::TimePeriod& time_period2) {
return time_period1.start_timestamp() < time_period2.start_timestamp();
};
std::vector<em::AppInfo> app_infos;
provider_->app_service_proxy.AppRegistryCache().ForEachApp(
[&app_infos, &activity_periods, &activity_compare,
this](const apps::AppUpdate& update) {
ActivityStorage::Activities& app_activity =
activity_periods[update.AppId()];
std::sort(app_activity.begin(), app_activity.end(), activity_compare);
app_infos.push_back(ConvertToAppInfo(update, app_activity));
});
return app_infos;
}
void AppInfoGenerator::OnReportingChanged(bool should_report) {
if (should_report_ == should_report) {
return;
}
should_report_ = should_report;
if (provider_) {
if (should_report) {
provider_->app_service_proxy.InstanceRegistry().AddObserver(this);
} else {
provider_->app_service_proxy.InstanceRegistry().RemoveObserver(this);
}
}
}
void AppInfoGenerator::OnReportedSuccessfully(const base::Time report_time) {
if (!provider_) {
return;
}
provider_->activity_storage.TrimActivityPeriods(
report_time.ToJavaTime(), base::Time::Max().ToJavaTime());
}
void AppInfoGenerator::OnWillReport() {
if (!provider_) {
return;
}
SetOpenDurationsToClosed(clock_.Now());
SetIdleDurationsToOpen();
}
void AppInfoGenerator::OnAffiliatedLogin(Profile* profile) {
if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
VLOG(1) << "No apps available. Will not track usage.";
return;
}
provider_ = std::make_unique<AppInfoGenerator::AppInfoProvider>(profile);
provider_->activity_storage.PruneActivityPeriods(
clock_.Now(), max_stored_past_activity_interval_);
if (should_report_) {
provider_->app_service_proxy.InstanceRegistry().AddObserver(this);
}
}
void AppInfoGenerator::OnAffiliatedLogout(Profile* profile) {
if (provider_) {
if (should_report_) {
provider_->app_service_proxy.InstanceRegistry().RemoveObserver(this);
}
provider_.reset();
}
}
void AppInfoGenerator::OnLocked() {
SetOpenDurationsToClosed(clock_.Now());
}
void AppInfoGenerator::OnUnlocked() {
SetIdleDurationsToOpen();
}
void AppInfoGenerator::OnResumeActive(base::Time suspend_time) {
SetOpenDurationsToClosed(suspend_time);
SetIdleDurationsToOpen();
}
const em::AppInfo AppInfoGenerator::ConvertToAppInfo(
const apps::AppUpdate& update,
const std::vector<em::TimePeriod>& app_activity) const {
em::AppInfo info;
bool is_web_app = update.AppType() == apps::mojom::AppType::kWeb;
if (!is_web_app) {
info.set_app_id(update.AppId());
info.set_app_name(update.Name());
} else {
const std::string launch_url = provider_->web_app_provider.registrar()
.GetAppStartUrl(update.AppId())
.GetOrigin()
.spec();
info.set_app_id(launch_url);
info.set_app_name(launch_url);
}
info.set_status(ExtractStatus(update.Readiness()));
info.set_version(update.Version());
info.set_app_type(ExtractAppType(update.AppType()));
*info.mutable_active_time_periods() = {app_activity.begin(),
app_activity.end()};
return info;
}
void AppInfoGenerator::SetOpenDurationsToClosed(base::Time end_time) {
if (!provider_) {
return;
}
provider_->app_service_proxy.InstanceRegistry().RemoveObserver(this);
for (auto const& app : app_instances_by_id_) {
const std::string& app_id = app.first;
base::Time start_time = app.second.get()->start_time;
provider_->activity_storage.AddActivityPeriod(start_time, end_time, app_id);
}
app_instances_by_id_.clear();
}
void AppInfoGenerator::SetIdleDurationsToOpen() {
if (!provider_) {
return;
}
base::Time start_time = clock_.Now();
provider_->app_service_proxy.InstanceRegistry().ForEachInstance(
[this, start_time](const apps::InstanceUpdate& update) {
if (update.State() & apps::InstanceState::kStarted) {
OpenUsageInterval(update.AppId(), update.InstanceKey(), start_time);
}
});
provider_->app_service_proxy.InstanceRegistry().AddObserver(this);
}
void AppInfoGenerator::OpenUsageInterval(
const std::string& app_id,
const apps::Instance::InstanceKey& instance_key,
const base::Time start_time) {
if (app_instances_by_id_.count(app_id) == 0) {
app_instances_by_id_[app_id] = std::make_unique<AppInstances>(start_time);
}
app_instances_by_id_[app_id]->running_instances.insert(instance_key);
}
void AppInfoGenerator::CloseUsageInterval(
const std::string& app_id,
const apps::Instance::InstanceKey& instance_key,
const base::Time end_time) {
if (app_instances_by_id_.count(app_id)) {
auto& app_instances = app_instances_by_id_[app_id];
app_instances->running_instances.erase(instance_key);
if (app_instances->running_instances.empty()) {
base::Time start_time = app_instances->start_time;
provider_->activity_storage.AddActivityPeriod(start_time, end_time,
app_id);
app_instances_by_id_.erase(app_id);
}
}
}
void AppInfoGenerator::OnInstanceUpdate(const apps::InstanceUpdate& update) {
if (!update.StateChanged()) {
return;
}
apps::InstanceState state = update.State();
const std::string& app_id = update.AppId();
auto instance_key = update.InstanceKey();
if (state & apps::InstanceState::kStarted) {
OpenUsageInterval(app_id, instance_key, update.LastUpdatedTime());
} else if (state & apps::InstanceState::kDestroyed) {
CloseUsageInterval(app_id, instance_key, update.LastUpdatedTime());
}
}
void AppInfoGenerator::OnInstanceRegistryWillBeDestroyed(
apps::InstanceRegistry* registry) {
registry->RemoveObserver(this);
}
} // namespace policy