OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ios/chrome/browser/metrics/first_user_action_recorder.h" |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/location.h" |
| 9 #include "base/metrics/histogram.h" |
| 10 #include "base/strings/string_util.h" |
| 11 #include "base/strings/stringprintf.h" |
| 12 #include "base/threading/thread_task_runner_handle.h" |
| 13 #include "ios/chrome/browser/ui/ui_util.h" |
| 14 #include "ios/web/public/web_thread.h" |
| 15 |
| 16 const char* kFirstUserActionNewTaskHistogramName[] = { |
| 17 "FirstUserAction.BackgroundTimeNewTaskHandset", |
| 18 "FirstUserAction.BackgroundTimeNewTaskTablet", |
| 19 }; |
| 20 const char* kFirstUserActionContinuationHistogramName[] = { |
| 21 "FirstUserAction.BackgroundTimeContinuationHandset", |
| 22 "FirstUserAction.BackgroundTimeContinuationTablet", |
| 23 }; |
| 24 const char* kFirstUserActionExpirationHistogramName[] = { |
| 25 "FirstUserAction.BackgroundTimeExpirationHandset", |
| 26 "FirstUserAction.BackgroundTimeExpirationTablet", |
| 27 }; |
| 28 const char* kFirstUserActionTypeHistogramName[] = { |
| 29 "FirstUserAction.HandsetUserActionType", |
| 30 "FirstUserAction.TabletUserActionType", |
| 31 }; |
| 32 |
| 33 namespace { |
| 34 // A list of actions that don't provide information about the user starting a |
| 35 // task or continuing an existing task. |
| 36 const char* kIgnoredActions[] = { |
| 37 "MobileOmniboxUse", |
| 38 "MobileBreakpadUploadAttempt", |
| 39 "MobileFirstUserAction_Continuation", |
| 40 "MobileFirstUserAction_Expiration", |
| 41 "MobileFirstUserAction_NewTask", |
| 42 "MobileMenuCloseAllTabs", |
| 43 "MobileMenuCloseAllIncognitoTabs", |
| 44 "MobileMenuCloseTab", |
| 45 "MobileNewTabOpened", |
| 46 "MobileTabClosed", |
| 47 "MobileTabStripCloseTab", |
| 48 "MobilePageLoaded", |
| 49 "MobilePageLoadedWithKeyboard", |
| 50 "MobileStackViewCloseTab", |
| 51 "MobileToolbarShowMenu", |
| 52 "MobileToolbarShowStackView", |
| 53 }; |
| 54 |
| 55 // A list of actions that should be 'rethrown' because subsequent actions may |
| 56 // be more indicative of the first user action type. 'Rethrowing' an action |
| 57 // puts a call to OnUserAction in the message queue so it is processed after |
| 58 // any other actions currently in the message queue. |
| 59 const char* kRethrownActions[] = { |
| 60 "MobileTabSwitched", |
| 61 }; |
| 62 |
| 63 // A list of actions that indicate a new task has been started. |
| 64 const char* kNewTaskActions[] = { |
| 65 "MobileMenuAllBookmarks", "MobileMenuHistory", |
| 66 "MobileMenuNewIncognitoTab", "MobileMenuNewTab", |
| 67 "MobileMenuOpenTabs", "MobileMenuVoiceSearch", |
| 68 "MobileNTPBookmark", "MobileNTPForeignSession", |
| 69 "MobileNTPMostVisited", "MobileNTPShowBookmarks", |
| 70 "MobileNTPShowMostVisited", "MobileNTPShowOpenTabs", |
| 71 "MobileNTPSwitchToBookmarks", "MobileNTPSwitchToMostVisited", |
| 72 "MobileNTPSwitchToOpenTabs", "MobileTabStripNewTab", |
| 73 "MobileToolbarNewTab", "MobileToolbarStackViewNewTab", |
| 74 "MobileToolbarVoiceSearch", "OmniboxInputInProgress", |
| 75 }; |
| 76 |
| 77 // Min and max values (in minutes) for the buckets in the duration histograms. |
| 78 const int kDurationHistogramMin = 5; |
| 79 const int kDurationHistogramMax = 48 * 60; |
| 80 |
| 81 // Number of buckets in the duration histograms. |
| 82 const int kDurationHistogramBucketCount = 50; |
| 83 |
| 84 } // namespace |
| 85 |
| 86 FirstUserActionRecorder::FirstUserActionRecorder( |
| 87 base::TimeDelta background_duration) |
| 88 : device_family_(IsIPadIdiom() ? TABLET : HANDSET), |
| 89 recorded_action_(false), |
| 90 action_pending_(false), |
| 91 background_duration_(background_duration), |
| 92 action_callback_(base::Bind(&FirstUserActionRecorder::OnUserAction, |
| 93 base::Unretained(this))) { |
| 94 base::SetRecordActionTaskRunner( |
| 95 web::WebThread::GetTaskRunnerForThread(web::WebThread::UI)); |
| 96 base::AddActionCallback(action_callback_); |
| 97 } |
| 98 |
| 99 FirstUserActionRecorder::~FirstUserActionRecorder() { |
| 100 base::RemoveActionCallback(action_callback_); |
| 101 } |
| 102 |
| 103 void FirstUserActionRecorder::Expire() { |
| 104 std::string log_message = "Recording 'Expiration' for first user action type"; |
| 105 RecordAction(EXPIRATION, log_message); |
| 106 } |
| 107 |
| 108 void FirstUserActionRecorder::RecordStartOnNTP() { |
| 109 std::string log_message = |
| 110 "Recording 'Start on NTP' for first user action type"; |
| 111 RecordAction(START_ON_NTP, log_message); |
| 112 } |
| 113 |
| 114 void FirstUserActionRecorder::OnUserAction(const std::string& action_name) { |
| 115 if (ShouldProcessAction(action_name)) { |
| 116 if (ArrayContainsString(kNewTaskActions, arraysize(kNewTaskActions), |
| 117 action_name.c_str())) { |
| 118 std::string log_message = base::StringPrintf( |
| 119 "Recording 'New task' for first user action type" |
| 120 " (user action: %s)", |
| 121 action_name.c_str()); |
| 122 RecordAction(NEW_TASK, log_message); |
| 123 } else { |
| 124 std::string log_message = base::StringPrintf( |
| 125 "Recording 'Continuation' for first user action " |
| 126 " type (user action: %s)", |
| 127 action_name.c_str()); |
| 128 RecordAction(CONTINUATION, log_message); |
| 129 } |
| 130 } |
| 131 } |
| 132 |
| 133 void FirstUserActionRecorder::RecordAction( |
| 134 const FirstUserActionType& action_type, |
| 135 const std::string& log_message) { |
| 136 if (!recorded_action_) { |
| 137 DVLOG(1) << log_message |
| 138 << " (background duration: " << background_duration_.InMinutes() |
| 139 << " minutes)"; |
| 140 UMA_HISTOGRAM_ENUMERATION(kFirstUserActionTypeHistogramName[device_family_], |
| 141 action_type, FIRST_USER_ACTION_TYPE_COUNT); |
| 142 recorded_action_ = true; |
| 143 switch (action_type) { |
| 144 case NEW_TASK: |
| 145 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 146 kFirstUserActionNewTaskHistogramName[device_family_], |
| 147 background_duration_.InMinutes(), kDurationHistogramMin, |
| 148 kDurationHistogramMax, kDurationHistogramBucketCount); |
| 149 break; |
| 150 case CONTINUATION: |
| 151 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 152 kFirstUserActionContinuationHistogramName[device_family_], |
| 153 background_duration_.InMinutes(), kDurationHistogramMin, |
| 154 kDurationHistogramMax, kDurationHistogramBucketCount); |
| 155 break; |
| 156 case EXPIRATION: |
| 157 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 158 kFirstUserActionExpirationHistogramName[device_family_], |
| 159 background_duration_.InMinutes(), kDurationHistogramMin, |
| 160 kDurationHistogramMax, kDurationHistogramBucketCount); |
| 161 break; |
| 162 case START_ON_NTP: |
| 163 break; |
| 164 default: |
| 165 NOTREACHED(); |
| 166 break; |
| 167 } |
| 168 } |
| 169 } |
| 170 |
| 171 bool FirstUserActionRecorder::ShouldProcessAction( |
| 172 const std::string& action_name) { |
| 173 if (recorded_action_) |
| 174 return false; |
| 175 |
| 176 if (!action_pending_ && |
| 177 ArrayContainsString(kRethrownActions, arraysize(kRethrownActions), |
| 178 action_name.c_str())) { |
| 179 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 180 FROM_HERE, base::Bind(&FirstUserActionRecorder::OnUserAction, |
| 181 base::Unretained(this), action_name)); |
| 182 action_pending_ = true; |
| 183 return false; |
| 184 } |
| 185 |
| 186 // Processed actions must either start with 'Mobile' or be in the |
| 187 // |new_task_actions_| whitelist. |
| 188 bool known_mobile_action = |
| 189 base::StartsWith(action_name, "Mobile", base::CompareCase::SENSITIVE) || |
| 190 ArrayContainsString(kNewTaskActions, arraysize(kNewTaskActions), |
| 191 action_name.c_str()); |
| 192 |
| 193 return known_mobile_action && |
| 194 !ArrayContainsString(kIgnoredActions, arraysize(kIgnoredActions), |
| 195 action_name.c_str()); |
| 196 } |
| 197 |
| 198 bool FirstUserActionRecorder::ArrayContainsString(const char* to_search[], |
| 199 const size_t to_search_size, |
| 200 const char* to_find) { |
| 201 for (size_t i = 0; i < to_search_size; ++i) { |
| 202 if (strcmp(to_find, to_search[i]) == 0) |
| 203 return true; |
| 204 } |
| 205 return false; |
| 206 } |
OLD | NEW |