Index: ios/chrome/browser/metrics/first_user_action_recorder.cc |
diff --git a/ios/chrome/browser/metrics/first_user_action_recorder.cc b/ios/chrome/browser/metrics/first_user_action_recorder.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d93836cd57efc69cc5476c97874c02f8440c2e1e |
--- /dev/null |
+++ b/ios/chrome/browser/metrics/first_user_action_recorder.cc |
@@ -0,0 +1,206 @@ |
+// Copyright 2013 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. |
+ |
+#include "ios/chrome/browser/metrics/first_user_action_recorder.h" |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/metrics/histogram.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#include "ios/web/public/web_thread.h" |
+ |
+const char* kFirstUserActionNewTaskHistogramName[] = { |
+ "FirstUserAction.BackgroundTimeNewTaskHandset", |
+ "FirstUserAction.BackgroundTimeNewTaskTablet", |
+}; |
+const char* kFirstUserActionContinuationHistogramName[] = { |
+ "FirstUserAction.BackgroundTimeContinuationHandset", |
+ "FirstUserAction.BackgroundTimeContinuationTablet", |
+}; |
+const char* kFirstUserActionExpirationHistogramName[] = { |
+ "FirstUserAction.BackgroundTimeExpirationHandset", |
+ "FirstUserAction.BackgroundTimeExpirationTablet", |
+}; |
+const char* kFirstUserActionTypeHistogramName[] = { |
+ "FirstUserAction.HandsetUserActionType", |
+ "FirstUserAction.TabletUserActionType", |
+}; |
+ |
+namespace { |
+// A list of actions that don't provide information about the user starting a |
+// task or continuing an existing task. |
+const char* kIgnoredActions[] = { |
+ "MobileOmniboxUse", |
+ "MobileBreakpadUploadAttempt", |
+ "MobileFirstUserAction_Continuation", |
+ "MobileFirstUserAction_Expiration", |
+ "MobileFirstUserAction_NewTask", |
+ "MobileMenuCloseAllTabs", |
+ "MobileMenuCloseAllIncognitoTabs", |
+ "MobileMenuCloseTab", |
+ "MobileNewTabOpened", |
+ "MobileTabClosed", |
+ "MobileTabStripCloseTab", |
+ "MobilePageLoaded", |
+ "MobilePageLoadedWithKeyboard", |
+ "MobileStackViewCloseTab", |
+ "MobileToolbarShowMenu", |
+ "MobileToolbarShowStackView", |
+}; |
+ |
+// A list of actions that should be 'rethrown' because subsequent actions may |
+// be more indicative of the first user action type. 'Rethrowing' an action |
+// puts a call to OnUserAction in the message queue so it is processed after |
+// any other actions currently in the message queue. |
+const char* kRethrownActions[] = { |
+ "MobileTabSwitched", |
+}; |
+ |
+// A list of actions that indicate a new task has been started. |
+const char* kNewTaskActions[] = { |
+ "MobileMenuAllBookmarks", "MobileMenuHistory", |
+ "MobileMenuNewIncognitoTab", "MobileMenuNewTab", |
+ "MobileMenuOpenTabs", "MobileMenuVoiceSearch", |
+ "MobileNTPBookmark", "MobileNTPForeignSession", |
+ "MobileNTPMostVisited", "MobileNTPShowBookmarks", |
+ "MobileNTPShowMostVisited", "MobileNTPShowOpenTabs", |
+ "MobileNTPSwitchToBookmarks", "MobileNTPSwitchToMostVisited", |
+ "MobileNTPSwitchToOpenTabs", "MobileTabStripNewTab", |
+ "MobileToolbarNewTab", "MobileToolbarStackViewNewTab", |
+ "MobileToolbarVoiceSearch", "OmniboxInputInProgress", |
+}; |
+ |
+// Min and max values (in minutes) for the buckets in the duration histograms. |
+const int kDurationHistogramMin = 5; |
+const int kDurationHistogramMax = 48 * 60; |
+ |
+// Number of buckets in the duration histograms. |
+const int kDurationHistogramBucketCount = 50; |
+ |
+} // namespace |
+ |
+FirstUserActionRecorder::FirstUserActionRecorder( |
+ base::TimeDelta background_duration) |
+ : device_family_(IsIPadIdiom() ? TABLET : HANDSET), |
+ recorded_action_(false), |
+ action_pending_(false), |
+ background_duration_(background_duration), |
+ action_callback_(base::Bind(&FirstUserActionRecorder::OnUserAction, |
+ base::Unretained(this))) { |
+ base::SetRecordActionTaskRunner( |
+ web::WebThread::GetTaskRunnerForThread(web::WebThread::UI)); |
+ base::AddActionCallback(action_callback_); |
+} |
+ |
+FirstUserActionRecorder::~FirstUserActionRecorder() { |
+ base::RemoveActionCallback(action_callback_); |
+} |
+ |
+void FirstUserActionRecorder::Expire() { |
+ std::string log_message = "Recording 'Expiration' for first user action type"; |
+ RecordAction(EXPIRATION, log_message); |
+} |
+ |
+void FirstUserActionRecorder::RecordStartOnNTP() { |
+ std::string log_message = |
+ "Recording 'Start on NTP' for first user action type"; |
+ RecordAction(START_ON_NTP, log_message); |
+} |
+ |
+void FirstUserActionRecorder::OnUserAction(const std::string& action_name) { |
+ if (ShouldProcessAction(action_name)) { |
+ if (ArrayContainsString(kNewTaskActions, arraysize(kNewTaskActions), |
+ action_name.c_str())) { |
+ std::string log_message = base::StringPrintf( |
+ "Recording 'New task' for first user action type" |
+ " (user action: %s)", |
+ action_name.c_str()); |
+ RecordAction(NEW_TASK, log_message); |
+ } else { |
+ std::string log_message = base::StringPrintf( |
+ "Recording 'Continuation' for first user action " |
+ " type (user action: %s)", |
+ action_name.c_str()); |
+ RecordAction(CONTINUATION, log_message); |
+ } |
+ } |
+} |
+ |
+void FirstUserActionRecorder::RecordAction( |
+ const FirstUserActionType& action_type, |
+ const std::string& log_message) { |
+ if (!recorded_action_) { |
+ DVLOG(1) << log_message |
+ << " (background duration: " << background_duration_.InMinutes() |
+ << " minutes)"; |
+ UMA_HISTOGRAM_ENUMERATION(kFirstUserActionTypeHistogramName[device_family_], |
+ action_type, FIRST_USER_ACTION_TYPE_COUNT); |
+ recorded_action_ = true; |
+ switch (action_type) { |
+ case NEW_TASK: |
+ UMA_HISTOGRAM_CUSTOM_COUNTS( |
+ kFirstUserActionNewTaskHistogramName[device_family_], |
+ background_duration_.InMinutes(), kDurationHistogramMin, |
+ kDurationHistogramMax, kDurationHistogramBucketCount); |
+ break; |
+ case CONTINUATION: |
+ UMA_HISTOGRAM_CUSTOM_COUNTS( |
+ kFirstUserActionContinuationHistogramName[device_family_], |
+ background_duration_.InMinutes(), kDurationHistogramMin, |
+ kDurationHistogramMax, kDurationHistogramBucketCount); |
+ break; |
+ case EXPIRATION: |
+ UMA_HISTOGRAM_CUSTOM_COUNTS( |
+ kFirstUserActionExpirationHistogramName[device_family_], |
+ background_duration_.InMinutes(), kDurationHistogramMin, |
+ kDurationHistogramMax, kDurationHistogramBucketCount); |
+ break; |
+ case START_ON_NTP: |
+ break; |
+ default: |
+ NOTREACHED(); |
+ break; |
+ } |
+ } |
+} |
+ |
+bool FirstUserActionRecorder::ShouldProcessAction( |
+ const std::string& action_name) { |
+ if (recorded_action_) |
+ return false; |
+ |
+ if (!action_pending_ && |
+ ArrayContainsString(kRethrownActions, arraysize(kRethrownActions), |
+ action_name.c_str())) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(&FirstUserActionRecorder::OnUserAction, |
+ base::Unretained(this), action_name)); |
+ action_pending_ = true; |
+ return false; |
+ } |
+ |
+ // Processed actions must either start with 'Mobile' or be in the |
+ // |new_task_actions_| whitelist. |
+ bool known_mobile_action = |
+ base::StartsWith(action_name, "Mobile", base::CompareCase::SENSITIVE) || |
+ ArrayContainsString(kNewTaskActions, arraysize(kNewTaskActions), |
+ action_name.c_str()); |
+ |
+ return known_mobile_action && |
+ !ArrayContainsString(kIgnoredActions, arraysize(kIgnoredActions), |
+ action_name.c_str()); |
+} |
+ |
+bool FirstUserActionRecorder::ArrayContainsString(const char* to_search[], |
+ const size_t to_search_size, |
+ const char* to_find) { |
+ for (size_t i = 0; i < to_search_size; ++i) { |
+ if (strcmp(to_find, to_search[i]) == 0) |
+ return true; |
+ } |
+ return false; |
+} |