Index: ios/chrome/app/application_delegate/metrics_mediator.mm |
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.mm b/ios/chrome/app/application_delegate/metrics_mediator.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..074ff8fc2a82c280642aff2138f4664520ca87f4 |
--- /dev/null |
+++ b/ios/chrome/app/application_delegate/metrics_mediator.mm |
@@ -0,0 +1,394 @@ |
+// 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/ios/weak_nsobject.h" |
+#include "base/mac/bind_objc_block.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" |
+ |
+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 url] == 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. |
+ base::mac::ScopedBlock<app_group::ProceduralBlockWithData> callback; |
+ if (enabled) { |
+ callback.reset( |
+ ^(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::BindBlock(^{ |
+ metrics->PushExternalLog(log); |
+ })); |
+ }, |
+ base::scoped_policy::RETAIN); |
+ } |
+ |
+ 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 |