| 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
|
|
|