| // Copyright 2016 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. |
| |
| #import "ios/chrome/app/application_delegate/metrics_mediator.h" |
| |
| #include "base/mac/bind_objc_block.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "components/crash/core/common/crash_keys.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/metrics/metrics_service.h" |
| #include "components/prefs/pref_service.h" |
| #import "ios/chrome/app/application_delegate/startup_information.h" |
| #include "ios/chrome/browser/application_context.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #include "ios/chrome/browser/crash_report/breakpad_helper.h" |
| #import "ios/chrome/browser/crash_report/crash_report_background_uploader.h" |
| #include "ios/chrome/browser/experimental_flags.h" |
| #include "ios/chrome/browser/metrics/first_user_action_recorder.h" |
| #import "ios/chrome/browser/metrics/previous_session_info.h" |
| #import "ios/chrome/browser/net/connection_type_observer_bridge.h" |
| #include "ios/chrome/browser/pref_names.h" |
| #import "ios/chrome/browser/tabs/tab.h" |
| #import "ios/chrome/browser/tabs/tab_model.h" |
| #import "ios/chrome/browser/ui/main/browser_view_information.h" |
| #include "ios/chrome/common/app_group/app_group_metrics_mainapp.h" |
| #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| #include "ios/public/provider/chrome/browser/distribution/app_distribution_provider.h" |
| #include "ios/web/public/web_thread.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| // The amount of time (in seconds) between two background fetch calls. |
| // TODO(crbug.com/496172): Re-enable background fetch. |
| const NSTimeInterval kBackgroundFetchIntervalDelay = |
| UIApplicationBackgroundFetchIntervalNever; |
| // The amount of time (in seconds) to wait for the user to start a new task. |
| const NSTimeInterval kFirstUserActionTimeout = 30.0; |
| } // namespace |
| |
| namespace metrics_mediator { |
| NSString* const kAppEnteredBackgroundDateKey = @"kAppEnteredBackgroundDate"; |
| } // namespace metrics_mediator_constants |
| |
| using metrics_mediator::kAppEnteredBackgroundDateKey; |
| |
| @interface MetricsMediator ()<CRConnectionTypeObserverBridge> { |
| // Whether or not the crash reports present at startup have been processed to |
| // determine if the last app lifetime ended in an OOM crash. |
| BOOL _hasProcessedCrashReportsPresentAtStartup; |
| |
| // Observer for the connection type. Contains a valid object only if the |
| // metrics setting is set to wifi-only. |
| std::unique_ptr<ConnectionTypeObserverBridge> connectionTypeObserverBridge_; |
| } |
| |
| // Starts or stops metrics recording and/or uploading. |
| - (void)setMetricsEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading; |
| // 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; |
| // Processes crash reports present at startup. |
| - (void)processCrashReportsPresentAtStartup; |
| // Starts or stops crash recording and/or uploading. |
| - (void)setBreakpadEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading; |
| // Starts or stops watching for wwan events. |
| - (void)setWatchWWANEnabled:(BOOL)enabled; |
| // Enable/disable transmission of accumulated logs and crash reports (dumps). |
| - (void)setReporting:(BOOL)enableReporting; |
| // Enable/Disable uploading crash reports. |
| - (void)setBreakpadUploadingEnabled:(BOOL)enableUploading; |
| // Returns YES if the metrics are enabled and the reporting is wifi-only. |
| - (BOOL)isMetricsReportingEnabledWifiOnly; |
| // 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 number of tabs with UMAHistogramCount100 and allows testing. |
| + (void)recordNumTabAtStartup:(int)numTabs; |
| // Logs the number of tabs with UMAHistogramCount100 and allows testing. |
| + (void)recordNumTabAtResume:(int)numTabs; |
| |
| @end |
| |
| @implementation MetricsMediator |
| |
| #pragma mark - Public methods. |
| |
| + (void)logStartupDuration:(id<StartupInformation>)startupInformation { |
| if (![startupInformation isColdStart]) |
| return; |
| |
| base::TimeDelta startDuration = |
| base::TimeTicks::Now() - [startupInformation appLaunchTime]; |
| if ([startupInformation startupParameters]) { |
| UMA_HISTOGRAM_TIMES("Startup.ColdStartWithExternalURLTime", startDuration); |
| } else { |
| UMA_HISTOGRAM_TIMES("Startup.ColdStartWithoutExternalURLTime", |
| startDuration); |
| } |
| } |
| |
| + (void)logDateInUserDefaults { |
| [[NSUserDefaults standardUserDefaults] |
| setObject:[NSDate date] |
| forKey:metrics_mediator::kAppEnteredBackgroundDateKey]; |
| } |
| |
| + (void)logLaunchMetricsWithStartupInformation: |
| (id<StartupInformation>)startupInformation |
| browserViewInformation: |
| (id<BrowserViewInformation>)browserViewInformation { |
| int numTabs = static_cast<int>([[browserViewInformation mainTabModel] count]); |
| if (startupInformation.isColdStart) { |
| [self recordNumTabAtStartup:numTabs]; |
| } else { |
| [self recordNumTabAtResume:numTabs]; |
| } |
| |
| if (UIAccessibilityIsVoiceOverRunning()) { |
| base::RecordAction( |
| base::UserMetricsAction("MobileVoiceOverActiveOnLaunch")); |
| } |
| |
| // 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]; |
| GURL ntpUrl = GURL(kChromeUINewTabURL); |
| |
| Tab* currentTab = [[browserViewInformation currentTabModel] currentTab]; |
| if (currentTab && currentTab.lastCommittedURL == ntpUrl) { |
| 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]; |
| } |
| } |
| |
| - (void)updateMetricsStateBasedOnPrefsUserTriggered:(BOOL)isUserTriggered { |
| BOOL optIn = [self areMetricsEnabled]; |
| BOOL allowUploading = [self isUploadingEnabled]; |
| BOOL wifiOnly = GetApplicationContext()->GetLocalState()->GetBoolean( |
| prefs::kMetricsReportingWifiOnly); |
| |
| if (isUserTriggered) |
| [self updateMetricsPrefsOnPermissionChange:optIn]; |
| [self setMetricsEnabled:optIn withUploading:allowUploading]; |
| [self setBreakpadEnabled:optIn withUploading:allowUploading]; |
| [self setWatchWWANEnabled:(optIn && wifiOnly)]; |
| } |
| |
| - (BOOL)areMetricsEnabled { |
| // If this if-def changes, it needs to be changed in |
| // IOSChromeMainParts::IsMetricsReportingEnabled and settings_egtest.mm. |
| #if defined(GOOGLE_CHROME_BUILD) |
| 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; |
| } |
| |
| - (BOOL)isUploadingEnabled { |
| BOOL optIn = [self areMetricsEnabled]; |
| BOOL wifiOnly = GetApplicationContext()->GetLocalState()->GetBoolean( |
| prefs::kMetricsReportingWifiOnly); |
| BOOL allowUploading = optIn; |
| if (optIn && wifiOnly) { |
| BOOL usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular( |
| net::NetworkChangeNotifier::GetConnectionType()); |
| allowUploading = !usingWWAN; |
| } |
| return allowUploading; |
| } |
| |
| #pragma mark - Internal methods. |
| |
| - (void)setMetricsEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading { |
| metrics::MetricsService* metrics = |
| GetApplicationContext()->GetMetricsService(); |
| DCHECK(metrics); |
| if (!metrics) |
| return; |
| if (enabled) { |
| [[UIApplication sharedApplication] |
| setMinimumBackgroundFetchInterval:kBackgroundFetchIntervalDelay]; |
| if (!metrics->recording_active()) |
| metrics->Start(); |
| |
| if (allowUploading) |
| metrics->EnableReporting(); |
| else |
| metrics->DisableReporting(); |
| } else { |
| if (metrics->recording_active()) |
| metrics->Stop(); |
| [[UIApplication sharedApplication] |
| setMinimumBackgroundFetchInterval: |
| UIApplicationBackgroundFetchIntervalNever]; |
| } |
| } |
| |
| - (void)setAppGroupMetricsEnabled:(BOOL)enabled { |
| metrics::MetricsService* metrics = |
| GetApplicationContext()->GetMetricsService(); |
| if (enabled) { |
| PrefService* prefs = GetApplicationContext()->GetLocalState(); |
| NSString* brandCode = |
| base::SysUTF8ToNSString(ios::GetChromeBrowserProvider() |
| ->GetAppDistributionProvider() |
| ->GetDistributionBrandCode()); |
| app_group::main_app::EnableMetrics( |
| base::SysUTF8ToNSString(metrics->GetClientId()), brandCode, |
| prefs->GetInt64(metrics::prefs::kInstallDate), |
| prefs->GetInt64(metrics::prefs::kMetricsReportingEnabledTimestamp)); |
| } else { |
| app_group::main_app::DisableMetrics(); |
| } |
| |
| // If metrics are enabled, process the logs. Otherwise, just delete them. |
| app_group::ProceduralBlockWithData callback; |
| if (enabled) { |
| callback = [^(NSData* log_content) { |
| std::string log(static_cast<const char*>([log_content bytes]), |
| static_cast<size_t>([log_content length])); |
| web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, |
| base::BindBlockArc(^{ |
| metrics->PushExternalLog(log); |
| })); |
| } copy]; |
| } |
| |
| web::WebThread::PostTask( |
| web::WebThread::FILE, FROM_HERE, |
| base::Bind(&app_group::main_app::ProcessPendingLogs, callback)); |
| } |
| |
| - (void)processCrashReportsPresentAtStartup { |
| _hasProcessedCrashReportsPresentAtStartup = YES; |
| |
| breakpad_helper::GetCrashReportCount(^(int crashReportCount) { |
| [[CrashReportBackgroundUploader sharedInstance] |
| setHasPendingCrashReportsToUploadAtStartup:(crashReportCount > 0)]; |
| }); |
| } |
| |
| - (void)setBreakpadEnabled:(BOOL)enabled withUploading:(BOOL)allowUploading { |
| if (enabled) { |
| breakpad_helper::SetEnabled(true); |
| |
| // Do some processing of the crash reports present at startup. Note that |
| // this processing must be done before uploading is enabled because once |
| // uploading starts the number of crash reports present will begin to |
| // decrease as they are uploaded. The ordering is ensured here because both |
| // the crash report processing and the upload enabling are handled by |
| // posting blocks to a single |dispath_queue_t| in BreakpadController. |
| if (!_hasProcessedCrashReportsPresentAtStartup && allowUploading) { |
| [self processCrashReportsPresentAtStartup]; |
| } |
| [self setBreakpadUploadingEnabled:(![[PreviousSessionInfo sharedInstance] |
| isFirstSessionAfterUpgrade] && |
| allowUploading)]; |
| } else { |
| breakpad_helper::SetEnabled(false); |
| } |
| } |
| |
| - (void)setWatchWWANEnabled:(BOOL)enabled { |
| if (!enabled) { |
| connectionTypeObserverBridge_.reset(); |
| return; |
| } |
| |
| if (!connectionTypeObserverBridge_) { |
| connectionTypeObserverBridge_.reset(new ConnectionTypeObserverBridge(self)); |
| } |
| } |
| |
| - (void)updateMetricsPrefsOnPermissionChange:(BOOL)enabled { |
| // TODO(crbug.com/635669): 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::kMetricsReportingEnabledTimestamp); |
| crash_keys::ClearMetricsClientId(); |
| } |
| } |
| |
| + (void)disableReporting { |
| breakpad_helper::SetUploadingEnabled(false); |
| metrics::MetricsService* metrics = |
| GetApplicationContext()->GetMetricsService(); |
| DCHECK(metrics); |
| metrics->DisableReporting(); |
| } |
| |
| + (void)applicationDidEnterBackground:(NSInteger)memoryWarningCount { |
| base::RecordAction(base::UserMetricsAction("MobileEnteredBackground")); |
| UMA_HISTOGRAM_COUNTS_100("MemoryWarning.OccurrencesPerSession", |
| memoryWarningCount); |
| } |
| |
| #pragma mark - CRConnectionTypeObserverBridge implementation |
| |
| - (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type { |
| BOOL wwanEnabled = net::NetworkChangeNotifier::IsConnectionCellular(type); |
| // Currently the MainController only cares about WWAN state for the metrics |
| // reporting preference. If it's disabled, or the wifi-only preference is |
| // not set, we don't care. In fact, we should not even be getting this call. |
| DCHECK([self isMetricsReportingEnabledWifiOnly]); |
| // |wwanEnabled| is true if a cellular connection such as EDGE or GPRS is |
| // used. Otherwise, either there is no connection available, or another link |
| // (such as WiFi) is used. |
| if (wwanEnabled) { |
| // If WWAN mode is on, wifi-only prefs should be disabled. |
| // For the crash reporter, we still want to record the crashes. |
| [self setBreakpadUploadingEnabled:NO]; |
| [self setReporting:NO]; |
| } else if ([self areMetricsEnabled]) { |
| // Double-check that the metrics reporting preference is enabled. |
| if (![[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]) |
| [self setBreakpadUploadingEnabled:YES]; |
| [self setReporting:YES]; |
| } |
| } |
| |
| #pragma mark - interfaces methods |
| |
| + (void)recordNumTabAtStartup:(int)numTabs { |
| UMA_HISTOGRAM_COUNTS_100("Tabs.CountAtStartup", numTabs); |
| } |
| |
| + (void)recordNumTabAtResume:(int)numTabs { |
| UMA_HISTOGRAM_COUNTS_100("Tabs.CountAtResume", numTabs); |
| } |
| |
| - (void)setBreakpadUploadingEnabled:(BOOL)enableUploading { |
| breakpad_helper::SetUploadingEnabled(enableUploading); |
| } |
| |
| - (void)setReporting:(BOOL)enableReporting { |
| if (enableReporting) { |
| GetApplicationContext()->GetMetricsService()->EnableReporting(); |
| } else { |
| GetApplicationContext()->GetMetricsService()->DisableReporting(); |
| } |
| } |
| |
| - (BOOL)isMetricsReportingEnabledWifiOnly { |
| return GetApplicationContext()->GetLocalState()->GetBoolean( |
| metrics::prefs::kMetricsReportingEnabled) && |
| GetApplicationContext()->GetLocalState()->GetBoolean( |
| prefs::kMetricsReportingWifiOnly); |
| } |
| |
| @end |