// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/app/application_delegate/metrics_mediator.h"
#import <mach/mach.h>
#import <sys/sysctl.h>
#import "base/functional/bind.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/time/time.h"
#import "build/branding_buildflags.h"
#import "components/crash/core/common/crash_keys.h"
#import "components/metrics/metrics_pref_names.h"
#import "components/metrics/metrics_service.h"
#import "components/metrics/metrics_switches.h"
#import "components/prefs/pref_service.h"
#import "components/previous_session_info/previous_session_info.h"
#import "components/signin/public/identity_manager/tribool.h"
#import "components/ukm/ios/ukm_reporting_ios_util.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/metric_kit_subscriber.h"
#import "ios/chrome/app/application_delegate/startup_information.h"
#import "ios/chrome/app/startup/ios_enable_sandbox_dump_buildflags.h"
#import "ios/chrome/browser/crash_report/model/crash_helper.h"
#import "ios/chrome/browser/default_browser/model/default_browser_interest_signals.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/metrics/model/first_user_action_recorder.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/connection_information.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/tab_group.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/browser/signin/model/signin_util.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/metrics.h"
#import "ios/chrome/browser/widget_kit/model/features.h"
#import "ios/chrome/common/app_group/app_group_metrics.h"
#import "ios/chrome/common/app_group/app_group_metrics_mainapp.h"
#import "ios/chrome/common/credential_provider/constants.h"
#import "ios/public/provider/chrome/browser/app_distribution/app_distribution_api.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "ios/web/public/web_state.h"
#import "url/gurl.h"
#if BUILDFLAG(ENABLE_WIDGET_KIT_EXTENSION)
#import "ios/chrome/browser/widget_kit/model/widget_metrics_util.h" // nogncheck
#endif
@class AppState;
namespace {
// The key to a NSUserDefaults entry logging the number of times classes are
// loaded before a scene is attached.
NSString* const kLoadTimePreferenceKey = @"LoadTimePreferenceKey";
// The amount of time (in seconds) to wait for the user to start a new task.
const NSTimeInterval kFirstUserActionTimeout = 30.0;
// Enum values for Startup.IOSColdStartType histogram.
// Entries should not be renumbered and numeric values should never be reused.
enum class ColdStartType : int {
// Regular cold start.
kRegular = 0,
// Cold start with FRE.
kFirstRun = 1,
// Cold start after a device restore.
kAfterDeviceRestore = 2,
// Cold start after a Chrome upgrade.
kAfterChromeUpgrade = 3,
// Cold start after a device restore and Chrome upgrade.
kAfterDeviceRestoreAndChromeUpgrade = 4,
// Unknown device restore.
kUnknownDeviceRestore = 5,
// Unknown device restore and Chrome upgrade.
kUnknownDeviceRestoreAndChromeUpgrade = 6,
kMaxValue = kUnknownDeviceRestoreAndChromeUpgrade,
};
// Enum representing the existing set of all open tabs age scenarios. Current
// values should not be renumbered. Please keep in sync with
// "IOSAllOpenTabsAge" in src/tools/metrics/histograms/enums.xml.
enum class TabsAgeGroup {
kLessThanOneDay = 0,
kOneToThreeDays = 1,
kThreeToSevenDays = 2,
kSevenToFourteenDays = 3,
kFourteenToThirtyDays = 4,
kMoreThanThirtyDays = 5,
kMaxValue = kMoreThanThirtyDays,
};
#if BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
// Returns time delta since app launch as retrieved from kernel info about
// the current process.
base::TimeDelta TimeDeltaSinceAppLaunchFromProcess() {
struct kinfo_proc info;
size_t length = sizeof(struct kinfo_proc);
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)getpid()};
const int kr = sysctl(mib, std::size(mib), &info, &length, nullptr, 0);
DCHECK_EQ(KERN_SUCCESS, kr);
const struct timeval time = info.kp_proc.p_starttime;
const NSTimeInterval time_since_1970 =
time.tv_sec + (time.tv_usec / (double)USEC_PER_SEC);
NSDate* date = [NSDate dateWithTimeIntervalSince1970:time_since_1970];
return base::Seconds(-date.timeIntervalSinceNow);
}
void DumpEnvironment(id<StartupInformation> startup_information) {
if (![[NSUserDefaults standardUserDefaults]
boolForKey:@"EnableDumpEnvironment"]) {
return;
}
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSError* error = nil;
NSString* document_directory = [paths objectAtIndex:0];
NSString* environment_directory =
[document_directory stringByAppendingPathComponent:@"environment"];
if (![[NSFileManager defaultManager]
fileExistsAtPath:environment_directory]) {
[[NSFileManager defaultManager] createDirectoryAtPath:environment_directory
withIntermediateDirectories:NO
attributes:nil
error:&error];
if (error) {
return;
}
}
NSDate* now_date = [NSDate date];
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMdd-HHmmss";
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
NSString* file_name = [formatter stringFromDate:now_date];
NSDictionary* environment = [[NSProcessInfo processInfo] environment];
base::TimeTicks now = base::TimeTicks::Now();
const base::TimeDelta processStartToNowTime =
TimeDeltaSinceAppLaunchFromProcess();
const base::TimeDelta mainToNowTime =
now - [startup_information appLaunchTime];
const base::TimeDelta didFinishLaunchingToNowTime =
now - [startup_information didFinishLaunchingTime];
const base::TimeDelta sceneConnectionToNowTime =
now - [startup_information firstSceneConnectionTime];
NSDictionary* dict = @{
@"environment" : environment,
@"now" : file_name,
@"processStartToNowTime" : @(processStartToNowTime.InMilliseconds()),
@"mainToNowTime" : @(mainToNowTime.InMilliseconds()),
@"didFinishLaunchingToNowTime" :
@(didFinishLaunchingToNowTime.InMilliseconds()),
@"sceneConnectionToNowTime" : @(sceneConnectionToNowTime.InMilliseconds()),
};
NSData* data =
[NSJSONSerialization dataWithJSONObject:dict
options:NSJSONWritingPrettyPrinted
error:&error];
if (error) {
return;
}
NSString* file_path =
[environment_directory stringByAppendingPathComponent:file_name];
[data writeToFile:file_path atomically:YES];
}
#endif // BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
// Returns the associated setting from inactive tab preference value.
InactiveTabsThresholdSetting InactiveTabsSettingFromPreference(int preference) {
switch (preference) {
case -1:
return InactiveTabsThresholdSetting::kNeverMove;
case 0:
return InactiveTabsThresholdSetting::kDefaultValue;
case 7:
return InactiveTabsThresholdSetting::kOneWeek;
case 14:
return InactiveTabsThresholdSetting::kTwoWeeks;
case 21:
return InactiveTabsThresholdSetting::kThreeWeeks;
default:
return InactiveTabsThresholdSetting::kUnknown;
}
}
const char kHistogramPrefixIncludingMismatch[] = "IOS.IncludingMismatch.";
const char kHistogramPrefix[] = "IOS.";
// Returns the warm start histogram prefix based on whether or not the metrics
// were collected for the same current app version or a previous version.
std::string WarmStartHistogramPrefix(bool version_mismatch) {
return version_mismatch ? kHistogramPrefixIncludingMismatch
: kHistogramPrefix;
}
} // namespace
namespace metrics_mediator {
NSString* const kAppEnteredBackgroundDateKey = @"kAppEnteredBackgroundDate";
NSString* const kAppDidFinishLaunchingConsecutiveCallsKey =
@"kAppDidFinishLaunchingConsecutiveCallsKey";
void RecordWidgetUsage(base::span<const HistogramNameCountPair> histograms) {
using base::SysNSStringToUTF8;
// Dictionary containing the respective metric for each NSUserDefault's key.
NSDictionary<NSString*, NSString*>* keyMetric = @{
app_group::
kContentExtensionDisplayCount : @"IOS.ContentExtension.DisplayCount",
app_group::
kSearchExtensionDisplayCount : @"IOS.SearchExtension.DisplayCount",
app_group::
kCredentialExtensionDisplayCount : @"IOS.CredentialExtension.DisplayCount",
app_group::
kCredentialExtensionReauthCount : @"IOS.CredentialExtension.ReauthCount",
app_group::
kCredentialExtensionCopyURLCount : @"IOS.CredentialExtension.CopyURLCount",
app_group::kCredentialExtensionCopyUsernameCount :
@"IOS.CredentialExtension.CopyUsernameCount",
app_group::kCredentialExtensionCopyUserDisplayNameCount :
@"IOS.CredentialExtension.CopyUserDisplayNameCount",
app_group::kCredentialExtensionCopyCreationDateCount :
@"IOS.CredentialExtension.CopyCreationDateCount",
app_group::kCredentialExtensionCopyPasswordCount :
@"IOS.CredentialExtension.CopyPasswordCount",
app_group::kCredentialExtensionShowPasswordCount :
@"IOS.CredentialExtension.ShowPasswordCount",
app_group::
kCredentialExtensionSearchCount : @"IOS.CredentialExtension.SearchCount",
app_group::kCredentialExtensionPasswordUseCount :
@"IOS.CredentialExtension.PasswordUseCount",
app_group::kCredentialExtensionPasskeyUseCount :
@"IOS.CredentialExtension.PasskeyUseCount",
app_group::kCredentialExtensionQuickPasswordUseCount :
@"IOS.CredentialExtension.QuickPasswordUseCount",
app_group::kCredentialExtensionQuickPasskeyUseCount :
@"IOS.CredentialExtension.QuickPasskeyUseCount",
app_group::kCredentialExtensionFetchPasswordFailureCount :
@"IOS.CredentialExtension.FetchPasswordFailure",
app_group::kCredentialExtensionFetchPasswordNilArgumentCount :
@"IOS.CredentialExtension.FetchPasswordNilArgument",
app_group::kCredentialExtensionKeychainSavePasswordFailureCount :
@"IOS.CredentialExtension.KeychainSavePasswordFailureCount",
app_group::kCredentialExtensionSaveCredentialFailureCount :
@"IOS.CredentialExtension.SaveCredentialFailureCount",
};
NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
for (NSString* key in keyMetric) {
int count = [shared_defaults integerForKey:key];
if (count != 0) {
base::UmaHistogramCounts1000(SysNSStringToUTF8(keyMetric[key]), count);
[shared_defaults setInteger:0 forKey:key];
}
}
for (const HistogramNameCountPair& pair : histograms) {
int maxSamples = pair.buckets;
// Check each possible bucket to see if it has any events to emit.
for (int bucket = 0; bucket < maxSamples; ++bucket) {
NSString* key = app_group::HistogramCountKey(pair.name, bucket);
int count = [shared_defaults integerForKey:key];
if (count != 0) {
[shared_defaults setInteger:0 forKey:key];
std::string histogramName = SysNSStringToUTF8(pair.name);
for (int emitCount = 0; emitCount < count; ++emitCount) {
base::UmaHistogramExactLinear(histogramName, bucket, maxSamples + 1);
}
}
}
}
}
} // namespace metrics_mediator
using metrics_mediator::kAppEnteredBackgroundDateKey;
using metrics_mediator::kAppDidFinishLaunchingConsecutiveCallsKey;
@interface MetricsMediator ()
// Starts or stops metrics recording.
- (void)setMetricsEnabled:(BOOL)enabled;
// Sets variables needed by the app_group application to collect UMA data.
// Process the pending logs produced by extensions.
// Called on start (cold and warm) and UMA settings change to update the
// collecting settings in extensions.
- (void)setAppGroupMetricsEnabled:(BOOL)enabled;
// Update metrics prefs on a permission (opt-in/out) change. When opting out,
// this clears various client ids. When opting in, this resets saving crash
// prefs, so as not to trigger upload of various stale data.
// Mirrors the function in metrics_reporting_state.cc.
- (void)updateMetricsPrefsOnPermissionChange:(BOOL)enabled;
// Logs the inactive tabs settings preference.
+ (void)recordInactiveTabsSettingsAtStartup:(int)preference;
// Logs the number of active tabs (based on the arm's definition of
// active/inactive).
+ (void)recordStartupActiveTabCount:(int)tabCount;
// Logs the number of inactive tabs (based on the arm's definition of
// active/inactive).
+ (void)recordStartupInactiveTabCount:(int)tabCount;
// Logs the number of tabs older than 21 days.
+ (void)recordStartupAbsoluteInactiveTabCount:(int)tabCount;
// Logs the number of pinned tabs at startup.
+ (void)recordStartupPinnedTabCount:(int)tabCount;
// Logs the number of tabs with UMAHistogramCount100 and allows testing.
+ (void)recordStartupTabCount:(int)tabCount;
// Logs the number of tab groups with UmaHistogramCounts1M.
+ (void)recordStartupTabGroupCount:(int)tabGroupCount;
// Logs the number of tabs per group with UMAHistogramCount10000.
+ (void)recordStartupTabsPerGroupCount:(int)tabsPerGroupCount;
// Logs the number of tabs with UMAHistogramCount100 and allows testing.
+ (void)recordResumeTabCount:(int)tabCount;
// Logs the number of NTP tabs with UMAHistogramCount100 and allows testing.
+ (void)recordStartupNTPTabCount:(int)tabCount;
// Logs the number of NTP tabs with UMAHistogramCount100 and allows testing.
+ (void)recordResumeNTPTabCount:(int)tabCount;
// Logs the number of live NTP tabs with UMAHistogramCount100 and allows
// testing.
+ (void)recordResumeLiveNTPTabCount:(int)tabCount;
// Logs the number of old (inactive for more than 7 days) tabs with
// UMAHistogramCount100 and allows testing.
+ (void)recordStartupOldTabCount:(int)tabCount;
// Logs the number of duplicated tabs with UMAHistogramCount100 and allows
// testing.
+ (void)recordStartupDuplicatedTabCount:(int)tabCount;
// Logs the age (time elapsed since creation) of each tab and allows testing.
+ (void)recordTabsAgeAtStartup:(const std::vector<base::TimeDelta>&)tabsAge;
// Returns a corresponding TabAgeGroup for provided `timeSinceCreation` time.
+ (TabsAgeGroup)tabsAgeGroupFromTimeSinceCreation:
(base::TimeDelta)timeSinceCreation;
// Logs the number of connected and disconnected scenes.
+ (void)recordConnectedAndDisconnectedSceneCount:(int)connectedScenes;
@end
@implementation MetricsMediator
// Indicates whether credential extension was used while chrome was inactive.
BOOL _credentialExtensionWasUsed = NO;
#pragma mark - Public methods.
+ (void)createStartupTrackingTask {
[MetricKitSubscriber createExtendedLaunchTask];
}
+ (void)logStartupDuration:(id<StartupInformation>)startupInformation {
if (![startupInformation isColdStart])
return;
[MetricKitSubscriber endExtendedLaunchTask];
base::TimeTicks now = base::TimeTicks::Now();
const base::TimeDelta mainToNowTime =
now - [startupInformation appLaunchTime];
const base::TimeDelta didFinishLaunchingToNowTime =
now - [startupInformation didFinishLaunchingTime];
const base::TimeDelta sceneConnectionToNowTime =
now - [startupInformation firstSceneConnectionTime];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
int consecutiveLoads = [defaults integerForKey:kLoadTimePreferenceKey];
[defaults removeObjectForKey:kLoadTimePreferenceKey];
int consecutiveDidFinishLaunching =
[defaults integerForKey:kAppDidFinishLaunchingConsecutiveCallsKey];
[defaults removeObjectForKey:kAppDidFinishLaunchingConsecutiveCallsKey];
base::UmaHistogramTimes("Startup.ColdStartFromMain", mainToNowTime);
base::UmaHistogramTimes("Startup.TimeFromMainToDidFinishLaunchingCall",
mainToNowTime - didFinishLaunchingToNowTime);
base::UmaHistogramTimes("Startup.TimeFromMainToSceneConnection",
mainToNowTime - sceneConnectionToNowTime);
base::UmaHistogramCounts100("Startup.ConsecutiveLoadsWithoutLaunch",
consecutiveLoads);
base::UmaHistogramCounts100(
"Startup.ConsecutiveDidFinishLaunchingWithoutLaunch",
consecutiveDidFinishLaunching);
#if BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
DumpEnvironment(startupInformation);
#endif // BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
}
+ (void)logDateInUserDefaults {
[[NSUserDefaults standardUserDefaults]
setObject:[NSDate date]
forKey:metrics_mediator::kAppEnteredBackgroundDateKey];
}
+ (void)logLaunchMetricsWithStartupInformation:
(id<StartupInformation>)startupInformation
connectedScenes:(NSArray<SceneState*>*)scenes {
RecordAndResetUkmLogSizeOnSuccessCounter();
int tabCount = 0;
int tabGroupCount = 0;
int pinnedTabCount = 0;
int NTPTabCount = 0;
int liveNTPTabCount = 0;
int oldTabCount = 0;
int duplicatedTabCount = 0;
int activeTabCount = 0;
int inactiveTabCount = 0;
int absoluteInactiveTabCount = 0;
// Amount of time after which a tab is considered as old.
constexpr base::TimeDelta kOldTabThreshold = base::Days(7);
// Amount of time after which a tab is considered as absolutely inactive.
constexpr base::TimeDelta kAbsoluteInactiveTabThreshold = base::Days(21);
NSMutableSet* uniqueURLs = [NSMutableSet set];
std::vector<base::TimeDelta> timesSinceCreation;
const base::Time now = base::Time::Now();
for (SceneState* scene in scenes) {
if (!scene.browserProviderInterface) {
// The scene might not yet be initiated.
// TODO(crbug.com/40123792): This will not be an issue when the tabs are
// counted in sessions instead of scenes.
continue;
}
const WebStateList* webStateList =
scene.browserProviderInterface.mainBrowserProvider.browser
->GetWebStateList();
const WebStateList* inactiveWebStateList =
scene.browserProviderInterface.mainBrowserProvider.inactiveBrowser
->GetWebStateList();
const int webStateListCount = webStateList->count();
const int inactiveWebStateListCount = inactiveWebStateList->count();
for (const TabGroup* group : webStateList->GetGroups()) {
tabGroupCount++;
[self recordStartupTabsPerGroupCount:group->range().count()];
}
tabCount += webStateListCount + inactiveWebStateListCount;
pinnedTabCount += webStateList->pinned_tabs_count();
activeTabCount += webStateListCount;
inactiveTabCount += inactiveWebStateListCount;
// All inactive tabs are inactive since minimum 7 days or more.
oldTabCount += inactiveWebStateListCount;
for (int i = 0; i < webStateListCount; i++) {
web::WebState* webState = webStateList->GetWebStateAt(i);
const bool wasWebStateRealized = webState->IsRealized();
const GURL& URL = webState->GetVisibleURL();
// Count NTPs.
if (IsURLNewTabPage(URL)) {
NTPTabCount++;
}
// Count duplicate URLs.
NSString* URLString = base::SysUTF8ToNSString(URL.GetWithoutRef().spec());
if ([uniqueURLs containsObject:URLString]) {
duplicatedTabCount++;
} else {
[uniqueURLs addObject:URLString];
}
// Count old tabs.
base::TimeDelta inactiveTime = now - webState->GetLastActiveTime();
if (inactiveTime > kOldTabThreshold) {
oldTabCount++;
// Count absolute inactive tabs.
if (inactiveTime > kAbsoluteInactiveTabThreshold) {
absoluteInactiveTabCount++;
}
}
// Calculate the age (time elapsed since creation) of WebState.
base::TimeDelta timeSinceCreation = now - webState->GetCreationTime();
timesSinceCreation.push_back(timeSinceCreation);
DCHECK_EQ(wasWebStateRealized, webState->IsRealized());
}
for (int i = 0; i < inactiveWebStateListCount; i++) {
web::WebState* webState = inactiveWebStateList->GetWebStateAt(i);
// Calculate the age (time elapsed since creation) of WebState.
base::TimeDelta timeSinceCreation = now - webState->GetCreationTime();
timesSinceCreation.push_back(timeSinceCreation);
// Calculate absolute inactive tabs.
base::TimeDelta inactiveTime =
base::Time::Now() - webState->GetLastActiveTime();
if (inactiveTime > kAbsoluteInactiveTabThreshold) {
absoluteInactiveTabCount++;
}
}
}
if (startupInformation.isColdStart) {
[self recordInactiveTabsSettingsAtStartup:
GetApplicationContext()->GetLocalState()->GetInteger(
prefs::kInactiveTabsTimeThreshold)];
[self recordStartupActiveTabCount:activeTabCount];
[self recordStartupInactiveTabCount:inactiveTabCount];
[self recordStartupAbsoluteInactiveTabCount:absoluteInactiveTabCount];
[self recordStartupPinnedTabCount:pinnedTabCount];
[self recordStartupTabCount:tabCount];
[self recordStartupTabGroupCount:tabGroupCount];
[self recordStartupNTPTabCount:NTPTabCount];
[self recordStartupOldTabCount:oldTabCount];
[self recordStartupDuplicatedTabCount:duplicatedTabCount];
[self recordTabsAgeAtStartup:timesSinceCreation];
[self recordAndResetWarmStartCount];
} else {
[[PreviousSessionInfo sharedInstance] incrementWarmStartCount];
[self recordResumeTabCount:tabCount];
[self recordResumeNTPTabCount:NTPTabCount];
// Only log at resume since there are likely no live NTPs on startup.
[self recordResumeLiveNTPTabCount:liveNTPTabCount];
}
[self recordConnectedAndDisconnectedSceneCount:scenes.count];
if (UIAccessibilityIsVoiceOverRunning()) {
base::RecordAction(
base::UserMetricsAction("MobileVoiceOverActiveOnLaunch"));
}
#if BUILDFLAG(ENABLE_WIDGET_KIT_EXTENSION)
[WidgetMetricsUtil logInstalledWidgets];
#endif
// Create the first user action recorder and schedule a task to expire it
// after some timeout. If unable to determine the last time the app entered
// the background (i.e. either first run or restore after crash), don't bother
// recording the first user action since fresh start wouldn't be triggered.
NSDate* lastAppClose = [[NSUserDefaults standardUserDefaults]
objectForKey:kAppEnteredBackgroundDateKey];
if (lastAppClose) {
NSTimeInterval interval = -[lastAppClose timeIntervalSinceNow];
[startupInformation
activateFirstUserActionRecorderWithBackgroundTime:interval];
SceneState* activeScene = nil;
for (SceneState* scene in scenes) {
if (scene.activationLevel == SceneActivationLevelForegroundActive) {
activeScene = scene;
break;
}
}
// Proceed if the active scene is initialized.
if (activeScene.browserProviderInterface) {
web::WebState* currentWebState =
activeScene.browserProviderInterface.currentBrowserProvider.browser
->GetWebStateList()
->GetActiveWebState();
if (currentWebState &&
currentWebState->GetLastCommittedURL() == kChromeUINewTabURL) {
startupInformation.firstUserActionRecorder->RecordStartOnNTP();
[startupInformation resetFirstUserActionRecorder];
} else {
[startupInformation
expireFirstUserActionRecorderAfterDelay:kFirstUserActionTimeout];
}
}
// Remove the value so it's not reused if the app crashes.
[[NSUserDefaults standardUserDefaults]
removeObjectForKey:kAppEnteredBackgroundDateKey];
}
// Log browser cold start for default browser promo experiment stats.
if (scenes.count != 0) {
LogBrowserLaunched(startupInformation.isColdStart);
}
if (!startupInformation.isColdStart) {
return;
}
signin::Tribool device_restore = IsFirstSessionAfterDeviceRestore();
ColdStartType sessionType;
if (startupInformation.isFirstRun) {
sessionType = ColdStartType::kFirstRun;
} else {
bool afterUpgrade =
[PreviousSessionInfo sharedInstance].isFirstSessionAfterUpgrade;
switch (device_restore) {
case signin::Tribool::kUnknown:
sessionType = afterUpgrade
? ColdStartType::kUnknownDeviceRestoreAndChromeUpgrade
: ColdStartType::kUnknownDeviceRestore;
break;
case signin::Tribool::kTrue:
sessionType = afterUpgrade
? ColdStartType::kAfterDeviceRestoreAndChromeUpgrade
: ColdStartType::kAfterDeviceRestore;
break;
case signin::Tribool::kFalse:
sessionType = afterUpgrade ? ColdStartType::kAfterChromeUpgrade
: ColdStartType::kRegular;
break;
}
}
base::UmaHistogramEnumeration("Startup.IOSColdStartType", sessionType);
}
- (void)updateMetricsStateBasedOnPrefsUserTriggered:(BOOL)isUserTriggered {
BOOL optIn = [self areMetricsEnabled];
if (isUserTriggered)
[self updateMetricsPrefsOnPermissionChange:optIn];
[self setMetricsEnabled:optIn];
crash_helper::SetEnabled(optIn);
[self setAppGroupMetricsEnabled:optIn];
[[MetricKitSubscriber sharedInstance] setEnabled:optIn];
}
- (void)notifyCredentialProviderWasUsed:(feature_engagement::Tracker*)tracker {
if (_credentialExtensionWasUsed) {
default_browser::NotifyCredentialExtensionUsed(tracker);
// Reset to avoid duplicate notifications.
_credentialExtensionWasUsed = NO;
}
}
- (BOOL)areMetricsEnabled {
if (metrics::IsMetricsReportingForceEnabled()) {
return YES;
}
// If this if-def changes, it needs to be changed in
// IOSChromeMainParts::IsMetricsReportingEnabled and settings_egtest.mm.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
BOOL optIn = GetApplicationContext()->GetLocalState()->GetBoolean(
metrics::prefs::kMetricsReportingEnabled);
#else
// If a startup crash has been requested, then pretend that metrics have been
// enabled, so that the app will go into recovery mode.
BOOL optIn = experimental_flags::IsStartupCrashEnabled();
#endif
return optIn;
}
#pragma mark - Internal methods.
- (void)setMetricsEnabled:(BOOL)enabled {
metrics::MetricsService* metrics =
GetApplicationContext()->GetMetricsService();
DCHECK(metrics);
if (!metrics)
return;
if (enabled) {
if (!metrics->recording_active())
metrics->Start();
metrics->EnableReporting();
} else {
if (metrics->recording_active())
metrics->Stop();
}
}
- (void)setAppGroupMetricsEnabled:(BOOL)enabled {
if (enabled) {
PrefService* prefs = GetApplicationContext()->GetLocalState();
NSString* brandCode =
base::SysUTF8ToNSString(ios::provider::GetBrandCode());
app_group::main_app::EnableMetrics(
base::SysUTF8ToNSString(
GetApplicationContext()->GetMetricsService()->GetClientId()),
brandCode, prefs->GetInt64(metrics::prefs::kInstallDate),
prefs->GetInt64(metrics::prefs::kMetricsReportingEnabledTimestamp));
// If metrics are enabled, process the logs. Otherwise, just delete them.
// TODO(crbug.com/40548746): remove related code.
} else {
app_group::main_app::DisableMetrics();
}
// Save CPE use information before it gets reset in RecordWidgetUsage until we
// are ready to log it into feture engagement tracker.
[self saveCredentialExtensionWasUsed];
// Histograms fired in extensions that need to be re-fired from the main app.
const metrics_mediator::HistogramNameCountPair histogramsFromExtension[] = {
{
@"IOS.CredentialExtension.PasswordCreated",
static_cast<int>(CPEPasswordCreated::kMaxValue) + 1,
},
{
@"IOS.CredentialExtension.NewCredentialUsername",
static_cast<int>(CPENewCredentialUsername::kMaxValue) + 1,
}};
metrics_mediator::RecordWidgetUsage(histogramsFromExtension);
}
- (void)updateMetricsPrefsOnPermissionChange:(BOOL)enabled {
// TODO(crbug.com/41268699): Consolidate with metrics_reporting_state.cc
// function.
metrics::MetricsService* metrics =
GetApplicationContext()->GetMetricsService();
DCHECK(metrics);
if (!metrics)
return;
if (enabled) {
// When a user opts in to the metrics reporting service, the previously
// collected data should be cleared to ensure that nothing is reported
// before a user opts in and all reported data is accurate.
if (!metrics->recording_active())
metrics->ClearSavedStabilityMetrics();
} else {
// Clear the client id pref when opting out.
// Note: Clearing client id will not affect the running state (e.g. field
// trial randomization), as the pref is only read on startup.
GetApplicationContext()->GetLocalState()->ClearPref(
metrics::prefs::kMetricsClientID);
GetApplicationContext()->GetLocalState()->ClearPref(
metrics::prefs::kMetricsProvisionalClientID);
GetApplicationContext()->GetLocalState()->ClearPref(
metrics::prefs::kMetricsReportingEnabledTimestamp);
crash_keys::ClearMetricsClientId();
}
}
+ (void)applicationDidEnterBackground:(NSInteger)memoryWarningCount {
base::RecordAction(base::UserMetricsAction("MobileEnteredBackground"));
task_vm_info task_info_data;
mach_msg_type_number_t count = sizeof(task_vm_info) / sizeof(natural_t);
kern_return_t result =
task_info(mach_task_self(), TASK_VM_INFO,
reinterpret_cast<task_info_t>(&task_info_data), &count);
if (result == KERN_SUCCESS) {
mach_vm_size_t footprint_mb = task_info_data.phys_footprint / 1024 / 1024;
base::UmaHistogramMemoryLargeMB(
"Memory.Browser.MemoryFootprint.OnBackground", footprint_mb);
}
}
- (void)saveCredentialExtensionWasUsed {
NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
int password_use_count = [shared_defaults
integerForKey:app_group::kCredentialExtensionPasswordUseCount];
int quick_password_use_count = [shared_defaults
integerForKey:app_group::kCredentialExtensionQuickPasswordUseCount];
int passkey_use_count = [shared_defaults
integerForKey:app_group::kCredentialExtensionPasskeyUseCount];
int quick_passkey_use_count = [shared_defaults
integerForKey:app_group::kCredentialExtensionQuickPasskeyUseCount];
if (password_use_count != 0 || quick_password_use_count != 0 ||
passkey_use_count != 0 || quick_passkey_use_count != 0) {
_credentialExtensionWasUsed = YES;
}
}
#pragma mark - interfaces methods
+ (void)recordInactiveTabsSettingsAtStartup:(int)preference {
UMA_HISTOGRAM_ENUMERATION(kInactiveTabsThresholdSettingHistogram,
InactiveTabsSettingFromPreference(preference));
}
+ (void)recordStartupActiveTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.ActiveCountAtStartup", tabCount);
}
+ (void)recordStartupInactiveTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.InactiveCountAtStartup", tabCount);
}
+ (void)recordStartupAbsoluteInactiveTabCount:(int)tabCount {
base::UmaHistogramCounts1M("Tabs.OldCountAtStartup2", tabCount);
}
+ (void)recordStartupPinnedTabCount:(int)tabCount {
base::UmaHistogramCounts1000("Tabs.PinnedCountAtStartup", tabCount);
}
+ (void)recordStartupTabCount:(int)tabCount {
base::UmaHistogramCounts1M("Tabs.CountAtStartup2", tabCount);
}
+ (void)recordStartupTabGroupCount:(int)tabGroupCount {
base::UmaHistogramCounts1M("TabGroups.CountAtStartup", tabGroupCount);
}
+ (void)recordStartupTabsPerGroupCount:(int)tabsPerGroupCount {
base::UmaHistogramCounts10000("TabGroups.TabsPerGroupCountAtStartup",
tabsPerGroupCount);
}
+ (void)recordResumeTabCount:(int)tabCount {
base::UmaHistogramCounts1M("Tabs.CountAtResume2", tabCount);
}
+ (void)recordStartupNTPTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.NTPCountAtStartup", tabCount);
}
+ (void)recordResumeNTPTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.NTPCountAtResume", tabCount);
}
+ (void)recordResumeLiveNTPTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.LiveNTPCountAtResume", tabCount);
}
+ (void)recordStartupOldTabCount:(int)tabCount {
base::UmaHistogramCounts1M("Tabs.UnusedCountAtStartup2", tabCount);
}
+ (void)recordStartupDuplicatedTabCount:(int)tabCount {
base::UmaHistogramCounts100("Tabs.DuplicatesCountAtStartup", tabCount);
}
+ (void)recordTabsAgeAtStartup:(const std::vector<base::TimeDelta>&)tabsAge {
for (const auto timeSinceCreation : tabsAge) {
TabsAgeGroup tabsAgeGroup =
[self tabsAgeGroupFromTimeSinceCreation:timeSinceCreation];
UMA_HISTOGRAM_ENUMERATION("Tabs.TimeSinceCreationAtStartup", tabsAgeGroup);
}
}
+ (void)recordConnectedAndDisconnectedSceneCount:(int)connectedScenes {
base::UmaHistogramCounts100("IOS.MultiWindow.ConnectedScenesCount",
connectedScenes);
base::UmaHistogramCounts100(
"IOS.MultiWindow.DisconnectedScenesCount",
UIApplication.sharedApplication.openSessions.count - connectedScenes);
}
+ (void)recordAndResetWarmStartCount {
bool afterUpgrade =
[PreviousSessionInfo sharedInstance].isFirstSessionAfterUpgrade;
const std::string prefix = WarmStartHistogramPrefix(afterUpgrade);
NSInteger warmStartCount =
[PreviousSessionInfo sharedInstance].warmStartCount;
base::UmaHistogramCounts100(prefix + "WarmStartCount", warmStartCount);
// The total number of launches from the session is the number of warm starts,
// plus the initial cold start.
base::UmaHistogramCounts100(prefix + "AppLaunchesPerSession",
warmStartCount + 1);
[[PreviousSessionInfo sharedInstance] resetWarmStartCount];
}
+ (TabsAgeGroup)tabsAgeGroupFromTimeSinceCreation:
(base::TimeDelta)timeSinceCreation {
if (timeSinceCreation < base::Days(1)) {
return TabsAgeGroup::kLessThanOneDay;
}
if (timeSinceCreation < base::Days(3)) {
return TabsAgeGroup::kOneToThreeDays;
}
if (timeSinceCreation < base::Days(7)) {
return TabsAgeGroup::kThreeToSevenDays;
}
if (timeSinceCreation < base::Days(14)) {
return TabsAgeGroup::kSevenToFourteenDays;
}
if (timeSinceCreation < base::Days(30)) {
return TabsAgeGroup::kFourteenToThirtyDays;
}
return TabsAgeGroup::kMoreThanThirtyDays;
}
@end