// Copyright 2020 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/child_accounts/family_user_app_metrics.h"
#include <memory>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "components/services/app_service/public/cpp/instance_registry.h"
#include "components/user_manager/user_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
namespace ash {
namespace {
// Recently launched apps this many days ago in the past will be recorded.
constexpr base::TimeDelta kOneDay = base::Days(1);
// UMA metrics for a snapshot count of installed and enabled extensions for a
// given family user.
constexpr char kInstalledExtensionsCountHistogramName[] =
"FamilyUser.InstalledExtensionsCount2";
constexpr char kEnabledExtensionsCountHistogramName[] =
"FamilyUser.EnabledExtensionsCount2";
// UMA metrics for a snapshot count of installed apps for a given family user.
constexpr char kUnknownAppsCountHistogramName[] =
"FamilyUser.UnknownAppsCount2";
constexpr char kArcAppsCountHistogramName[] = "FamilyUser.ArcAppsCount2";
constexpr char kBuiltInAppsCountHistogramName[] =
"FamilyUser.BuiltInAppsCount2";
constexpr char kCrostiniAppsCountHistogramName[] =
"FamilyUser.CrostiniAppsCount2";
// The InstalledExtensionsCount only includes regular browser extensions and
// themes. This counter only includes apps. The two counters are mutually
// exclusive.
constexpr char kExtensionAppsCountHistogramName[] =
"FamilyUser.ExtensionAppsCount2";
constexpr char kWebAppsCountHistogramName[] = "FamilyUser.WebAppsCount2";
constexpr char kPluginVmAppsCountHistogramName[] =
"FamilyUser.PluginVmAppsCount2";
constexpr char kStandaloneBrowserAppsCountHistogramName[] = "FamilyUser.LacrosAppsCount2";
constexpr char kRemoteAppsCountHistogramName[] = "FamilyUser.RemoteAppsCount2";
constexpr char kBorealisAppsCountHistogramName[] =
"FamilyUser.BorealisAppsCount2";
constexpr char kBruschettaAppsCountHistogramName[] =
"FamilyUser.BruschettaAppsCount2";
constexpr char kSystemWebAppsCountHistogramName[] =
"FamilyUser.SystemWebAppsCount2";
constexpr char kStandaloneBrowserChromeAppCountHistogramName[] =
"FamilyUser.LacrosChromeAppsCount2";
// TODO(agawronska): Add metrics for extensions, possibly differentiating Ash
// from Lacros (AKA StandaloneBrowser).
const char* GetAppsCountHistogramName(apps::AppType app_type) {
switch (app_type) {
case apps::AppType::kUnknown:
// Extensions are recorded separately, and AppService only has some
// extensions with file browser handlers.
case apps::AppType::kExtension:
case apps::AppType::kStandaloneBrowserExtension:
return kUnknownAppsCountHistogramName;
case apps::AppType::kArc:
return kArcAppsCountHistogramName;
case apps::AppType::kBuiltIn:
return kBuiltInAppsCountHistogramName;
case apps::AppType::kCrostini:
return kCrostiniAppsCountHistogramName;
case apps::AppType::kChromeApp:
return kExtensionAppsCountHistogramName;
case apps::AppType::kWeb:
return kWebAppsCountHistogramName;
case apps::AppType::kPluginVm:
return kPluginVmAppsCountHistogramName;
case apps::AppType::kStandaloneBrowser:
return kStandaloneBrowserAppsCountHistogramName;
case apps::AppType::kRemote:
return kRemoteAppsCountHistogramName;
case apps::AppType::kBorealis:
return kBorealisAppsCountHistogramName;
case apps::AppType::kBruschetta:
return kBruschettaAppsCountHistogramName;
case apps::AppType::kSystemWeb:
return kSystemWebAppsCountHistogramName;
case apps::AppType::kStandaloneBrowserChromeApp:
return kStandaloneBrowserChromeAppCountHistogramName;
}
}
} // namespace
// static
std::unique_ptr<FamilyUserAppMetrics> FamilyUserAppMetrics::Create(
Profile* profile) {
auto metrics = base::WrapUnique(new FamilyUserAppMetrics(profile));
metrics->Init();
return metrics;
}
FamilyUserAppMetrics::FamilyUserAppMetrics(Profile* profile)
: extension_registry_(extensions::ExtensionRegistry::Get(profile)),
app_registry_(&apps::AppServiceProxyFactory::GetForProfile(profile)
->AppRegistryCache()),
instance_registry_(&apps::AppServiceProxyFactory::GetForProfile(profile)
->InstanceRegistry()),
first_report_on_current_device_(
user_manager::UserManager::Get()->IsCurrentUserNew()) {
DCHECK(extension_registry_);
DCHECK(app_registry_);
app_registry_cache_observer_.Observe(app_registry_);
DCHECK(instance_registry_);
}
FamilyUserAppMetrics::~FamilyUserAppMetrics() = default;
// static
const char*
FamilyUserAppMetrics::GetInstalledExtensionsCountHistogramNameForTest() {
return kInstalledExtensionsCountHistogramName;
}
const char*
FamilyUserAppMetrics::GetEnabledExtensionsCountHistogramNameForTest() {
return kEnabledExtensionsCountHistogramName;
}
// static
const char* FamilyUserAppMetrics::GetAppsCountHistogramNameForTest(
apps::AppType app_type) {
return GetAppsCountHistogramName(app_type);
}
void FamilyUserAppMetrics::Init() {
for (const auto app_type : app_registry_->InitializedAppTypes()) {
OnAppTypeInitialized(app_type);
}
}
void FamilyUserAppMetrics::OnNewDay() {
// Ignores the first report during OOBE. Apps and extensions may sync slowly
// after the OOBE process, biasing the metrics downwards toward zero.
if (first_report_on_current_device_) {
first_report_on_current_device_ = false;
return;
}
should_record_metrics_on_new_day_ = true;
RecordInstalledExtensionsCount();
RecordEnabledExtensionsCount();
for (const auto& app_type : ready_app_types_)
RecordRecentlyUsedAppsCount(app_type);
}
void FamilyUserAppMetrics::OnAppTypeInitialized(apps::AppType app_type) {
DCHECK(!base::Contains(ready_app_types_, app_type));
// Skip the extension app type, because extensions are recorded separately,
// and AppService only has some extensions with file browser handlers.
if (app_type == apps::AppType::kExtension)
return;
ready_app_types_.insert(app_type);
if (should_record_metrics_on_new_day_)
RecordRecentlyUsedAppsCount(app_type);
}
void FamilyUserAppMetrics::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
DCHECK_EQ(cache, app_registry_);
app_registry_cache_observer_.Reset();
}
void FamilyUserAppMetrics::OnAppUpdate(const apps::AppUpdate& update) {}
bool FamilyUserAppMetrics::IsAppTypeReady(apps::AppType app_type) const {
return base::Contains(ready_app_types_, app_type);
}
void FamilyUserAppMetrics::RecordInstalledExtensionsCount() {
int extensions_count = 0;
const extensions::ExtensionSet all_installed_extensions =
extension_registry_->GenerateInstalledExtensionsSet();
for (const auto& extension : all_installed_extensions) {
if (extensions::Manifest::IsComponentLocation(extension->location()))
continue;
if (extension->is_extension() || extension->is_theme())
extensions_count++;
}
// If a family user has more than a thousand extensions installed, then that
// count is going into an overflow bucket. We don't expect this scenario to
// happen often.
base::UmaHistogramCounts1000(kInstalledExtensionsCountHistogramName,
extensions_count);
}
void FamilyUserAppMetrics::RecordEnabledExtensionsCount() {
int extensions_count = 0;
for (const auto& extension : extension_registry_->enabled_extensions()) {
if (extensions::Manifest::IsComponentLocation(extension->location()))
continue;
if (extension->is_extension() || extension->is_theme())
extensions_count++;
}
// If a family user has more than a thousand extensions enabled, then that
// count is going into an overflow bucket. We don't expect this scenario to
// happen often.
base::UmaHistogramCounts1000(kEnabledExtensionsCountHistogramName,
extensions_count);
}
void FamilyUserAppMetrics::RecordRecentlyUsedAppsCount(apps::AppType app_type) {
int app_count = 0;
base::Time now = base::Time::Now();
// The below will execute synchronously.
app_registry_->ForEachApp(
[app_type, now, this, &app_count](const apps::AppUpdate& update) {
if (update.AppType() != app_type)
return;
// Only count apps that have been used recently.
if (now - update.LastLaunchTime() <= kOneDay ||
IsAppWindowOpen(update.AppId())) {
app_count++;
}
});
// If a family user has more than a thousand apps installed, then that count
// is going into an overflow bucket. We don't expect this scenario to happen
// often.
const std::string histogram_name = GetAppsCountHistogramName(app_type);
base::UmaHistogramCounts1000(histogram_name, app_count);
}
bool FamilyUserAppMetrics::IsAppWindowOpen(const std::string& app_id) {
// An app is active if it has an open window.
return instance_registry_->ContainsAppId(app_id);
}
} // namespace ash