// Copyright 2021 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/apps/app_service/metrics/app_platform_metrics_utils.h"
#include <string_view>
#include "base/containers/fixed_flat_map.h"
#include "base/metrics/histogram_functions.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/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/guest_os/guest_os_shelf_utils.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/metrics/usertype_by_devicetype_metrics_provider.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "chromeos/components/mgs/managed_guest_session_utils.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/app_constants/constants.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/instance_registry.h"
#include "components/services/app_service/public/cpp/instance_update.h"
#include "components/sync/base/data_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_service_utils.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
namespace {
constexpr auto kAppTypeNameMap =
base::MakeFixedFlatMap<std::string_view, apps::AppTypeName>({
{apps::kArcHistogramName, apps::AppTypeName::kArc},
{apps::kBuiltInHistogramName, apps::AppTypeName::kBuiltIn},
{apps::kCrostiniHistogramName, apps::AppTypeName::kCrostini},
{apps::kChromeAppHistogramName, apps::AppTypeName::kChromeApp},
{apps::kWebAppHistogramName, apps::AppTypeName::kWeb},
{apps::kPluginVmHistogramName, apps::AppTypeName::kPluginVm},
{apps::kStandaloneBrowserHistogramName,
apps::AppTypeName::kStandaloneBrowser},
{apps::kRemoteHistogramName, apps::AppTypeName::kRemote},
{apps::kBorealisHistogramName, apps::AppTypeName::kBorealis},
{apps::kSystemWebAppHistogramName, apps::AppTypeName::kSystemWeb},
{apps::kChromeBrowserHistogramName, apps::AppTypeName::kChromeBrowser},
{apps::kStandaloneBrowserChromeAppHistogramName,
apps::AppTypeName::kStandaloneBrowserChromeApp},
{apps::kExtensionHistogramName, apps::AppTypeName::kExtension},
{apps::kStandaloneBrowserExtensionHistogramName,
apps::AppTypeName::kStandaloneBrowserExtension},
{apps::kStandaloneBrowserWebAppHistogramName,
apps::AppTypeName::kStandaloneBrowserWebApp},
{apps::kBruschettaHistogramName, apps::AppTypeName::kBruschetta},
});
constexpr char kInstallReasonUnknownHistogram[] = "Unknown";
constexpr char kInstallReasonSystemHistogram[] = "System";
constexpr char kInstallReasonPolicyHistogram[] = "Policy";
constexpr char kInstallReasonOemHistogram[] = "Oem";
constexpr char kInstallReasonPreloadHistogram[] = "Preload";
constexpr char kInstallReasonSyncHistogram[] = "Sync";
constexpr char kInstallReasonUserHistogram[] = "User";
constexpr char kInstallReasonSubAppHistogram[] = "SubApp";
constexpr char kInstallReasonKioskHistogram[] = "Kiosk";
constexpr char kInstallReasonCommandLineHistogram[] = "CommandLine";
// Determines what app type a Chrome App should be logged as based on its launch
// container and app id. In particular, Chrome apps in tabs are logged as part
// of Chrome browser.
apps::AppTypeName GetAppTypeNameForChromeApp(Profile* profile,
const std::string& app_id,
apps::LaunchContainer container) {
if (app_id == app_constants::kChromeAppId) {
return apps::AppTypeName::kChromeBrowser;
}
DCHECK(profile);
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile);
DCHECK(registry);
const extensions::Extension* extension =
registry->GetInstalledExtension(app_id);
if (!extension || !extension->is_app()) {
return apps::AppTypeName::kUnknown;
}
if (CanLaunchViaEvent(extension)) {
return apps::AppTypeName::kChromeApp;
}
switch (container) {
case apps::LaunchContainer::kLaunchContainerWindow:
return apps::AppTypeName::kChromeApp;
case apps::LaunchContainer::kLaunchContainerTab:
return apps::AppTypeName::kChromeBrowser;
default:
break;
}
apps::LaunchContainer launch_container = extensions::GetLaunchContainer(
extensions::ExtensionPrefs::Get(profile), extension);
if (launch_container == apps::LaunchContainer::kLaunchContainerTab) {
return apps::AppTypeName::kChromeBrowser;
}
return apps::AppTypeName::kChromeApp;
}
apps::AppTypeName GetWebAppTypeName() {
return crosapi::browser_util::IsLacrosEnabled()
? apps::AppTypeName::kStandaloneBrowserWebApp
: apps::AppTypeName::kWeb;
}
bool UkmReportingIsAllowedForAppInManagedGuestSession(
const std::string& app_id,
const apps::AppRegistryCache& cache) {
CHECK(chromeos::IsManagedGuestSession());
bool is_allowed = false;
cache.ForOneApp(app_id, [&is_allowed](const apps::AppUpdate& app) {
is_allowed = app.InstallReason() == apps::InstallReason::kSystem ||
app.InstallReason() == apps::InstallReason::kPolicy ||
app.InstallReason() == apps::InstallReason::kOem ||
app.InstallReason() == apps::InstallReason::kDefault;
});
return is_allowed;
}
} // namespace
namespace apps {
constexpr base::TimeDelta kMinDuration = base::Seconds(1);
constexpr base::TimeDelta kMaxUsageDuration = base::Minutes(5);
constexpr int kDurationBuckets = 100;
constexpr int kUsageTimeBuckets = 50;
AppTypeName GetAppTypeNameForWebApp(Profile* profile,
const std::string& app_id,
apps::LaunchContainer container) {
AppTypeName default_type_name = crosapi::browser_util::IsLacrosEnabled()
? AppTypeName::kStandaloneBrowser
: AppTypeName::kChromeBrowser;
AppTypeName type_name = default_type_name;
WindowMode window_mode = WindowMode::kBrowser;
AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache().ForOneApp(
app_id, [&type_name, &window_mode](const AppUpdate& update) {
DCHECK(update.AppType() == AppType::kWeb ||
update.AppType() == AppType::kSystemWeb);
// For system web apps, the install source is |kSystem|.
// The app type may be kSystemWeb (system web apps in Ash when
// Lacros web apps are enabled), or kWeb (all other cases).
type_name = (update.InstallReason() == InstallReason::kSystem)
? AppTypeName::kSystemWeb
: AppTypeName::kWeb;
window_mode = update.WindowMode();
});
if (type_name != AppTypeName::kWeb) {
return type_name;
}
switch (container) {
case apps::LaunchContainer::kLaunchContainerWindow:
return GetWebAppTypeName();
case apps::LaunchContainer::kLaunchContainerTab:
return default_type_name;
default:
break;
}
return window_mode == WindowMode::kBrowser ? default_type_name
: GetWebAppTypeName();
}
AppTypeName GetAppTypeNameForStandaloneBrowserChromeApp(
Profile* profile,
const std::string& app_id,
apps::LaunchContainer container) {
AppTypeName app_type_name = AppTypeName::kStandaloneBrowser;
WindowMode window_mode = WindowMode::kUnknown;
AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache().ForOneApp(
app_id, [&app_type_name, &window_mode](const AppUpdate& update) {
DCHECK(update.AppType() == AppType::kStandaloneBrowserChromeApp);
// For platform apps, app type name is kStandaloneBrowserChromeApp;
if (update.IsPlatformApp().value_or(false)) {
app_type_name = AppTypeName::kStandaloneBrowserChromeApp;
return;
}
window_mode = update.WindowMode();
});
if (app_type_name == AppTypeName::kStandaloneBrowserChromeApp) {
return app_type_name;
}
switch (container) {
case apps::LaunchContainer::kLaunchContainerWindow:
return AppTypeName::kStandaloneBrowserChromeApp;
case apps::LaunchContainer::kLaunchContainerTab:
return AppTypeName::kStandaloneBrowser;
case apps::LaunchContainer::kLaunchContainerPanelDeprecated:
case apps::LaunchContainer::kLaunchContainerNone:
break;
}
return window_mode == WindowMode::kWindow ||
window_mode == WindowMode::kTabbedWindow
? AppTypeName::kStandaloneBrowserChromeApp
: AppTypeName::kStandaloneBrowser;
}
bool IsAshBrowserWindow(aura::Window* window) {
Browser* browser = chrome::FindBrowserWithWindow(window->GetToplevelWindow());
if (!browser || browser->is_type_app() || browser->is_type_app_popup()) {
return false;
}
return true;
}
bool IsLacrosBrowserWindow(Profile* profile, aura::Window* window) {
if (!crosapi::browser_util::IsLacrosEnabled()) {
return false;
}
bool ret = false;
AppServiceProxyFactory::GetForProfile(profile)
->InstanceRegistry()
.ForInstancesWithWindow(window, [&](const apps::InstanceUpdate& update) {
if (update.AppId() == app_constants::kLacrosAppId) {
ret = true;
}
});
return ret;
}
bool IsLacrosWindow(aura::Window* window) {
return window->GetProperty(chromeos::kAppTypeKey) ==
chromeos::AppType::LACROS;
}
bool IsAppOpenedInTab(AppTypeName app_type_name, const std::string& app_id) {
return (app_type_name == apps::AppTypeName::kChromeBrowser &&
app_id != app_constants::kChromeAppId) ||
(app_type_name == apps::AppTypeName::kStandaloneBrowser &&
app_id != app_constants::kLacrosAppId);
}
bool IsAppOpenedWithBrowserWindow(Profile* profile,
AppType app_type,
const std::string& app_id) {
if (app_type == AppType::kWeb || app_type == AppType::kSystemWeb ||
app_type == AppType::kExtension ||
app_type == AppType::kStandaloneBrowser ||
app_type == AppType::kStandaloneBrowserChromeApp ||
app_type == AppType::kStandaloneBrowserExtension) {
return true;
}
if (app_type != AppType::kChromeApp) {
return false;
}
DCHECK(profile);
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile);
DCHECK(registry);
const extensions::Extension* extension =
registry->GetInstalledExtension(app_id);
return extension && !extension->is_platform_app();
}
AppTypeName GetAppTypeNameForWebAppWindow(Profile* profile,
const std::string& app_id,
aura::Window* window) {
if (IsAshBrowserWindow(window)) {
return AppTypeName::kChromeBrowser;
}
if (GetAppTypeNameForWebApp(profile, app_id,
apps::LaunchContainer::kLaunchContainerNone) ==
AppTypeName::kSystemWeb) {
return AppTypeName::kSystemWeb;
}
return IsLacrosBrowserWindow(profile, window)
? AppTypeName::kStandaloneBrowser
: GetWebAppTypeName();
}
AppTypeName GetAppTypeNameForWindow(Profile* profile,
AppType app_type,
const std::string& app_id,
aura::Window* window) {
switch (app_type) {
case AppType::kUnknown:
return apps::AppTypeName::kUnknown;
case AppType::kArc:
return apps::AppTypeName::kArc;
case AppType::kBuiltIn:
return apps::AppTypeName::kBuiltIn;
case AppType::kCrostini:
return apps::AppTypeName::kCrostini;
case AppType::kChromeApp:
return IsAshBrowserWindow(window) ? apps::AppTypeName::kChromeBrowser
: apps::AppTypeName::kChromeApp;
case AppType::kWeb:
return GetAppTypeNameForWebAppWindow(profile, app_id, window);
case AppType::kPluginVm:
return apps::AppTypeName::kPluginVm;
case AppType::kStandaloneBrowser:
return apps::AppTypeName::kStandaloneBrowser;
case AppType::kRemote:
return apps::AppTypeName::kRemote;
case AppType::kBorealis:
return apps::AppTypeName::kBorealis;
case AppType::kSystemWeb:
return apps::AppTypeName::kSystemWeb;
case AppType::kStandaloneBrowserChromeApp:
return IsLacrosBrowserWindow(profile, window)
? AppTypeName::kStandaloneBrowser
: AppTypeName::kStandaloneBrowserChromeApp;
case AppType::kExtension:
return apps::AppTypeName::kExtension;
case AppType::kStandaloneBrowserExtension:
return apps::AppTypeName::kStandaloneBrowserExtension;
case AppType::kBruschetta:
return apps::AppTypeName::kBruschetta;
}
}
std::string GetAppTypeHistogramName(apps::AppTypeName app_type_name) {
switch (app_type_name) {
case apps::AppTypeName::kUnknown:
return std::string();
case apps::AppTypeName::kArc:
return kArcHistogramName;
case apps::AppTypeName::kBuiltIn:
return kBuiltInHistogramName;
case apps::AppTypeName::kCrostini:
return kCrostiniHistogramName;
case apps::AppTypeName::kChromeApp:
return kChromeAppHistogramName;
case apps::AppTypeName::kWeb:
return kWebAppHistogramName;
case apps::AppTypeName::kPluginVm:
return kPluginVmHistogramName;
case apps::AppTypeName::kStandaloneBrowser:
return kStandaloneBrowserHistogramName;
case apps::AppTypeName::kRemote:
return kRemoteHistogramName;
case apps::AppTypeName::kBorealis:
return kBorealisHistogramName;
case apps::AppTypeName::kSystemWeb:
return kSystemWebAppHistogramName;
case apps::AppTypeName::kChromeBrowser:
return kChromeBrowserHistogramName;
case apps::AppTypeName::kStandaloneBrowserChromeApp:
return kStandaloneBrowserChromeAppHistogramName;
case apps::AppTypeName::kExtension:
return kExtensionHistogramName;
case apps::AppTypeName::kStandaloneBrowserExtension:
return kStandaloneBrowserExtensionHistogramName;
case apps::AppTypeName::kStandaloneBrowserWebApp:
return kStandaloneBrowserWebAppHistogramName;
case apps::AppTypeName::kBruschetta:
return kBruschettaHistogramName;
}
}
AppTypeName GetAppTypeNameFromString(const std::string& app_type_name) {
auto it = kAppTypeNameMap.find(app_type_name);
return it != kAppTypeNameMap.end() ? it->second : apps::AppTypeName::kUnknown;
}
std::string GetInstallReason(InstallReason install_reason) {
switch (install_reason) {
case apps::InstallReason::kUnknown:
return kInstallReasonUnknownHistogram;
case apps::InstallReason::kSystem:
return kInstallReasonSystemHistogram;
case apps::InstallReason::kPolicy:
return kInstallReasonPolicyHistogram;
case apps::InstallReason::kOem:
return kInstallReasonOemHistogram;
case apps::InstallReason::kDefault:
return kInstallReasonPreloadHistogram;
case apps::InstallReason::kSync:
return kInstallReasonSyncHistogram;
case apps::InstallReason::kUser:
return kInstallReasonUserHistogram;
case apps::InstallReason::kSubApp:
return kInstallReasonSubAppHistogram;
case apps::InstallReason::kKiosk:
return kInstallReasonKioskHistogram;
case apps::InstallReason::kCommandLine:
return kInstallReasonCommandLineHistogram;
}
}
bool ShouldRecordAppKM(Profile* profile) {
// Bypass AppKM App Sync check for Demo Mode devices to collect app metrics.
if (ash::DemoSession::IsDeviceInDemoMode()) {
return true;
}
// Bypass AppKM App Sync check in Kiosk and MGS to collect app metrics.
if (chromeos::IsKioskSession() || chromeos::IsManagedGuestSession()) {
return true;
}
switch (syncer::GetUploadToGoogleState(
SyncServiceFactory::GetForProfile(profile), syncer::DataType::APPS)) {
case syncer::UploadState::NOT_ACTIVE:
return false;
case syncer::UploadState::INITIALIZING:
// Note that INITIALIZING is considered good enough, because syncing apps
// is known to be enabled, and transient errors don't really matter here.
case syncer::UploadState::ACTIVE:
return true;
}
}
bool ShouldRecordAppKMForAppId(Profile* profile,
const AppRegistryCache& cache,
const std::string& app_id) {
if (!ShouldRecordAppKM(profile)) {
return false;
}
if (chromeos::IsManagedGuestSession() &&
!UkmReportingIsAllowedForAppInManagedGuestSession(app_id, cache)) {
return false;
}
return true;
}
bool ShouldRecordAppKMForAppTypeName(AppType app_type) {
switch (app_type) {
case AppType::kArc:
case AppType::kBuiltIn:
case AppType::kChromeApp:
case AppType::kWeb:
case AppType::kSystemWeb:
case AppType::kCrostini:
case AppType::kBorealis:
case AppType::kExtension:
case AppType::kStandaloneBrowser:
case AppType::kStandaloneBrowserChromeApp:
case AppType::kStandaloneBrowserExtension:
return true;
case AppType::kBruschetta:
case AppType::kUnknown:
case AppType::kPluginVm:
case AppType::kRemote:
return false;
}
}
int GetUserTypeByDeviceTypeMetrics() {
const user_manager::User* primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
DCHECK(primary_user);
UserTypeByDeviceTypeMetricsProvider::UserSegment user_segment =
UserTypeByDeviceTypeMetricsProvider::UserSegment::kUnmanaged;
// In some tast tests, primary_user->is_profile_created() might return false
// for some unknown reasons.
if (primary_user->is_profile_created()) {
Profile* profile =
ash::ProfileHelper::Get()->GetProfileByUser(primary_user);
DCHECK(profile);
user_segment = UserTypeByDeviceTypeMetricsProvider::GetUserSegment(profile);
}
policy::BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
policy::MarketSegment device_segment =
connector->GetEnterpriseMarketSegment();
return UserTypeByDeviceTypeMetricsProvider::ConstructUmaValue(user_segment,
device_segment);
}
AppTypeName GetAppTypeName(Profile* profile,
AppType app_type,
const std::string& app_id,
apps::LaunchContainer container) {
switch (app_type) {
case AppType::kUnknown:
return apps::AppTypeName::kUnknown;
case AppType::kArc:
return apps::AppTypeName::kArc;
case AppType::kBuiltIn:
return apps::AppTypeName::kBuiltIn;
case AppType::kCrostini:
return apps::AppTypeName::kCrostini;
case AppType::kChromeApp:
return GetAppTypeNameForChromeApp(profile, app_id, container);
case AppType::kWeb:
return GetAppTypeNameForWebApp(profile, app_id, container);
case AppType::kPluginVm:
return apps::AppTypeName::kPluginVm;
case AppType::kStandaloneBrowser:
return apps::AppTypeName::kStandaloneBrowser;
case AppType::kRemote:
return apps::AppTypeName::kRemote;
case AppType::kBorealis:
return apps::AppTypeName::kBorealis;
case AppType::kSystemWeb:
return apps::AppTypeName::kSystemWeb;
case AppType::kStandaloneBrowserChromeApp:
return GetAppTypeNameForStandaloneBrowserChromeApp(profile, app_id,
container);
case AppType::kExtension:
return apps::AppTypeName::kExtension;
case AppType::kStandaloneBrowserExtension:
return apps::AppTypeName::kStandaloneBrowserExtension;
case AppType::kBruschetta:
return apps::AppTypeName::kBruschetta;
}
}
AppType GetAppType(Profile* profile, const std::string& app_id) {
DCHECK(AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile));
auto type = apps::AppServiceProxyFactory::GetForProfile(profile)
->AppRegistryCache()
.GetAppType(app_id);
if (type != AppType::kUnknown) {
return type;
}
if (guest_os::IsCrostiniShelfAppId(profile, app_id)) {
return AppType::kCrostini;
}
return AppType::kUnknown;
}
bool IsSystemWebApp(Profile* profile, const std::string& app_id) {
AppType app_type = GetAppType(profile, app_id);
InstallReason install_reason;
apps::AppServiceProxyFactory::GetForProfile(profile)
->AppRegistryCache()
.ForOneApp(app_id, [&install_reason](const apps::AppUpdate& update) {
install_reason = update.InstallReason();
});
return app_type == AppType::kSystemWeb ||
install_reason == apps::InstallReason::kSystem;
}
} // namespace apps