// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_APPS_APP_SERVICE_METRICS_APP_DISCOVERY_METRICS_H_
#define CHROME_BROWSER_APPS_APP_SERVICE_METRICS_APP_DISCOVERY_METRICS_H_
#include <map>
#include <optional>
#include <set>
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/unguessable_token.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/instance_registry.h"
namespace apps {
// Represents the different state changes of interest for app-discovery. Keep
// in-sync with definition in structured.xml.
enum class AppStateChange {
kInactive = 0,
kActive = 1,
kClosed = 2,
};
// Records metrics related to app discovery and app usage. This should only be
// used in Chrome OS.
//
// No metrics should be recorded if app-sync is off.
class AppDiscoveryMetrics : public AppPlatformMetrics::Observer,
public apps::AppCapabilityAccessCache::Observer,
InstanceRegistry::Observer {
public:
AppDiscoveryMetrics(
Profile* profile,
const apps::AppRegistryCache& app_registry_cache,
InstanceRegistry& instance_registry,
AppPlatformMetrics* app_platform_metrics,
apps::AppCapabilityAccessCache& app_capability_access_cache);
~AppDiscoveryMetrics() override;
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
// Returns the string identifier to be logged in app discovery metrics for the
// given `package_id`.
//
// This can be used for apps which aren't installed yet (but have, for
// example, been shown in an app discovery surface), and so aren't registered
// in App Service and don't have an App ID.
//
// This returns the same value as `GetAppStringToRecord(app_id, app_type)`
// would if the package was installed. Returns std::nullopt if metrics for
// this package shouldn't be recorded.
static std::optional<std::string> GetAppStringToRecordForPackage(
const PackageId& package_id);
// AppPlatformMetrics::Observer
void OnAppInstalled(const std::string& app_id,
AppType app_type,
InstallSource app_install_source,
InstallReason app_install_reason,
InstallTime app_install_time) override;
void OnAppLaunched(const std::string& app_id,
AppType app_type,
LaunchSource launch_source) override;
void OnAppUninstalled(const std::string& app_id,
AppType app_type,
UninstallSource app_uninstall_source) override;
void OnAppPlatformMetricsDestroyed() override;
// apps::AppCapabilityAccessCache::Observer
void OnCapabilityAccessUpdate(
const apps::CapabilityAccessUpdate& update) override;
void OnAppCapabilityAccessCacheWillBeDestroyed(
apps::AppCapabilityAccessCache* cache) override;
// InstanceRegistry::Observer
void OnInstanceUpdate(const InstanceUpdate& instance_update) override;
void OnInstanceRegistryWillBeDestroyed(InstanceRegistry* cache) override;
private:
// Returns whether app sync is enabled for |profile_| and it's allowed to
// record AppKM for |app_id|.
bool ShouldRecordAppKMForAppId(const std::string& app_id);
// Returns true if there is an active instance of an app other than
// |exclude_instance_id|. If |exclude_instance_id| is nullopt, then all
// instances will be checked.
bool IsAnyAppInstanceActive(
const std::string& app_id,
std::optional<base::UnguessableToken> exclude_instance_id = std::nullopt);
// Records app state metrics if there has been a change.
void RecordAppState(const InstanceUpdate& instance_update);
// Checks whether |instance_update| moves from an active app state to inactive
// app state. If there are any other instances that are active other than
// the instance specified in |instance_update|, then the app is still
// considered to be active and will return false.
//
// If there was no previous state, the check for whether the previous state is
// Active will be ignored.
bool IsUpdateActiveToInactive(const InstanceUpdate& instance_update);
// Checks whether |instance_update| moves from an inactive app state to active
// app state. If there are any other instances that are active other than
// the instance specified in |instance_update|, then the app is still
// considered to be active and will return false.
//
// If there was no previous state, the check for whether the previous state is
// Inactive will be ignored.
bool IsUpdateInactiveToActive(const InstanceUpdate& instance_update);
bool IsStateInactive(InstanceState instance_state);
bool IsStateActive(InstanceState instance_state);
void RecordAppActive(const InstanceUpdate& instance_update);
void RecordAppInactive(const InstanceUpdate& instance_update);
void RecordAppClosed(const InstanceUpdate& instance_update);
// Adds an app based on |id| to the install list of apps.
//
// Returns true if the |id| was not previously in the install set and has been
// added.
bool AddAppInstall(const std::string& id);
// Removes an app based on |id| to the install list of apps.
//
// Returns true if the |id| was in the install set and has been removed.
bool RemoveAppInstall(const std::string& id);
// Returns true if app |id| is in |app_installed_|.
bool IsAppInstalled(const std::string& id);
// Returns true if the list of apps that are tracked is at capacity.
bool IsAppListAtCapacity();
// Builds a list based on current |app_installed_|.
base::Value::List BuildAppInstalledList();
// Returns the string identifier to be logged for the given |profile_| and
// |hashed_app_id|.
//
// For most apps, this should return the same string used to generate the URL
// string key for UKM. A lot of these URLs are hashed before being collected.
// For more information, refer to //components/ukm/app_source_url_recorder.h.
//
// ARC app package names are collected unhashed since the app package names
// are public information on the play store.
std::string GetAppStringToRecord(const std::string& hashed_app_id,
AppType app_type);
// Profile for which apps discovery metrics are being recorded for.
raw_ptr<Profile> profile_;
const raw_ref<const AppRegistryCache> app_registry_cache_;
// Instance of AppPlatformMetrics |this| is observing.
raw_ptr<AppPlatformMetrics> app_platform_metrics_ = nullptr;
// Map associating instance_ids to current state.
std::map<base::UnguessableToken, InstanceState> instance_to_state_;
// A set of installed events by |profile_|.
std::set<std::string> apps_installed_;
// Map associating app_ids to instance_ids.
std::map<std::string, std::set<base::UnguessableToken>>
app_id_to_instance_ids_;
// Observations.
base::ScopedObservation<apps::AppCapabilityAccessCache,
apps::AppCapabilityAccessCache::Observer>
app_capability_observation_{this};
base::ScopedObservation<InstanceRegistry, InstanceRegistry::Observer>
instance_registry_observation_{this};
};
} // namespace apps
#endif // CHROME_BROWSER_APPS_APP_SERVICE_METRICS_APP_DISCOVERY_METRICS_H_