1 // Copyright (c) 2012 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 "ash/touch/touch_uma.h" 6 7 #include "ash/metrics/user_metrics_recorder.h" 8 #include "ash/shell.h" 9 #include "base/metrics/histogram.h" 10 #include "base/strings/stringprintf.h" 11 #include "ui/aura/env.h" 12 #include "ui/aura/window.h" 13 #include "ui/aura/window_event_dispatcher.h" 14 #include "ui/aura/window_property.h" 15 #include "ui/events/event.h" 16 #include "ui/events/event_utils.h" 17 #include "ui/gfx/point_conversions.h" 18 19 #if defined(USE_XI2_MT) 20 #include <X11/extensions/XInput2.h> 21 #include <X11/Xlib.h> 22 #endif 23 24 namespace { 25 26 struct WindowTouchDetails { 27 // Move and start times of the touch points. The key is the touch-id. 28 std::map<int, base::TimeDelta> last_move_time_; 29 std::map<int, base::TimeDelta> last_start_time_; 30 31 // The first and last positions of the touch points. 32 std::map<int, gfx::Point> start_touch_position_; 33 std::map<int, gfx::Point> last_touch_position_; 34 35 // Last time-stamp of the last touch-end event. 36 base::TimeDelta last_release_time_; 37 38 // Stores the time of the last touch released on this window (if there was a 39 // multi-touch gesture on the window, then this is the release-time of the 40 // last touch on the window). 41 base::TimeDelta last_mt_time_; 42 }; 43 44 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails, 45 kWindowTouchDetails, 46 NULL); 47 } 48 49 namespace ash { 50 51 // static 52 TouchUMA* TouchUMA::GetInstance() { 53 return Singleton<TouchUMA>::get(); 54 } 55 56 void TouchUMA::RecordGestureEvent(aura::Window* target, 57 const ui::GestureEvent& event) { 58 GestureActionType action = FindGestureActionType(target, event); 59 RecordGestureAction(action); 60 61 if (event.type() == ui::ET_GESTURE_END && 62 event.details().touch_points() == 2) { 63 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); 64 if (!details) { 65 LOG(ERROR) << "Window received gesture events without receiving any touch" 66 " events"; 67 return; 68 } 69 details->last_mt_time_ = event.time_stamp(); 70 } 71 } 72 73 void TouchUMA::RecordGestureAction(GestureActionType action) { 74 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT) 75 return; 76 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action, 77 GESTURE_ACTION_COUNT); 78 } 79 80 void TouchUMA::RecordTouchEvent(aura::Window* target, 81 const ui::TouchEvent& event) { 82 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius", 83 static_cast<int>(std::max(event.radius_x(), event.radius_y())), 84 1, 500, 100); 85 86 UpdateTouchState(event); 87 88 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); 89 if (!details) { 90 details = new WindowTouchDetails; 91 target->SetProperty(kWindowTouchDetails, details); 92 } 93 94 // Record the location of the touch points. 95 const int kBucketCountForLocation = 100; 96 const gfx::Rect bounds = target->GetRootWindow()->bounds(); 97 const int bucket_size_x = std::max(1, 98 bounds.width() / kBucketCountForLocation); 99 const int bucket_size_y = std::max(1, 100 bounds.height() / kBucketCountForLocation); 101 102 gfx::Point position = event.root_location(); 103 104 // Prefer raw event location (when available) over calibrated location. 105 if (event.HasNativeEvent()) { 106 #if defined(USE_XI2_MT) 107 XEvent* xevent = event.native_event(); 108 CHECK_EQ(GenericEvent, xevent->type); 109 XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data); 110 if (xievent->evtype == XI_TouchBegin || 111 xievent->evtype == XI_TouchUpdate || 112 xievent->evtype == XI_TouchEnd) { 113 XIDeviceEvent* device_event = 114 static_cast<XIDeviceEvent*>(xevent->xcookie.data); 115 position.SetPoint(static_cast<int>(device_event->event_x), 116 static_cast<int>(device_event->event_y)); 117 } else { 118 position = ui::EventLocationFromNative(event.native_event()); 119 } 120 #else 121 position = ui::EventLocationFromNative(event.native_event()); 122 #endif 123 position = gfx::ToFlooredPoint( 124 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor())); 125 } 126 127 position.set_x(std::min(bounds.width() - 1, std::max(0, position.x()))); 128 position.set_y(std::min(bounds.height() - 1, std::max(0, position.y()))); 129 130 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX", 131 position.x() / bucket_size_x, 132 0, kBucketCountForLocation, kBucketCountForLocation + 1); 133 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY", 134 position.y() / bucket_size_y, 135 0, kBucketCountForLocation, kBucketCountForLocation + 1); 136 137 if (event.type() == ui::ET_TOUCH_PRESSED) { 138 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 139 UMA_TOUCHSCREEN_TAP_DOWN); 140 141 details->last_start_time_[event.touch_id()] = event.time_stamp(); 142 details->start_touch_position_[event.touch_id()] = event.root_location(); 143 details->last_touch_position_[event.touch_id()] = event.location(); 144 145 if (details->last_release_time_.ToInternalValue()) { 146 // Measuring the interval between a touch-release and the next 147 // touch-start is probably less useful when doing multi-touch (e.g. 148 // gestures, or multi-touch friendly apps). So count this only if the user 149 // hasn't done any multi-touch during the last 30 seconds. 150 base::TimeDelta diff = event.time_stamp() - details->last_mt_time_; 151 if (diff.InSeconds() > 30) { 152 base::TimeDelta gap = event.time_stamp() - details->last_release_time_; 153 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd", 154 gap.InMilliseconds()); 155 } 156 } 157 158 // Record the number of touch-points currently active for the window. 159 const int kMaxTouchPoints = 10; 160 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints", 161 details->last_start_time_.size(), 162 1, kMaxTouchPoints, kMaxTouchPoints + 1); 163 } else if (event.type() == ui::ET_TOUCH_RELEASED) { 164 if (details->last_start_time_.count(event.touch_id())) { 165 base::TimeDelta duration = event.time_stamp() - 166 details->last_start_time_[event.touch_id()]; 167 // Look for touches that were [almost] stationary for a long time. 168 const double kLongStationaryTouchDuration = 10; 169 const int kLongStationaryTouchDistanceSquared = 100; 170 if (duration.InSecondsF() > kLongStationaryTouchDuration) { 171 gfx::Vector2d distance = event.root_location() - 172 details->start_touch_position_[event.touch_id()]; 173 if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) { 174 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration", 175 duration.InSeconds(), 176 kLongStationaryTouchDuration, 177 1000, 178 20); 179 } 180 } 181 } 182 details->last_start_time_.erase(event.touch_id()); 183 details->last_move_time_.erase(event.touch_id()); 184 details->start_touch_position_.erase(event.touch_id()); 185 details->last_touch_position_.erase(event.touch_id()); 186 details->last_release_time_ = event.time_stamp(); 187 } else if (event.type() == ui::ET_TOUCH_MOVED) { 188 int distance = 0; 189 if (details->last_touch_position_.count(event.touch_id())) { 190 gfx::Point lastpos = details->last_touch_position_[event.touch_id()]; 191 distance = 192 std::abs(lastpos.x() - event.x()) + std::abs(lastpos.y() - event.y()); 193 } 194 195 if (details->last_move_time_.count(event.touch_id())) { 196 base::TimeDelta move_delay = event.time_stamp() - 197 details->last_move_time_[event.touch_id()]; 198 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval", 199 move_delay.InMilliseconds(), 200 1, 50, 25); 201 } 202 203 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50); 204 205 details->last_move_time_[event.touch_id()] = event.time_stamp(); 206 details->last_touch_position_[event.touch_id()] = event.location(); 207 } 208 } 209 210 TouchUMA::TouchUMA() 211 : is_single_finger_gesture_(false), 212 touch_in_progress_(false), 213 burst_length_(0) { 214 } 215 216 TouchUMA::~TouchUMA() { 217 } 218 219 void TouchUMA::UpdateTouchState(const ui::TouchEvent& event) { 220 if (event.type() == ui::ET_TOUCH_PRESSED) { 221 if (!touch_in_progress_) { 222 is_single_finger_gesture_ = true; 223 base::TimeDelta difference = event.time_stamp() - last_touch_down_time_; 224 if (difference > base::TimeDelta::FromMilliseconds(250)) { 225 if (burst_length_) { 226 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst", 227 std::min(burst_length_, 100)); 228 } 229 burst_length_ = 1; 230 } else { 231 ++burst_length_; 232 } 233 } else { 234 is_single_finger_gesture_ = false; 235 } 236 touch_in_progress_ = true; 237 last_touch_down_time_ = event.time_stamp(); 238 } else if (event.type() == ui::ET_TOUCH_RELEASED) { 239 if (!aura::Env::GetInstance()->is_touch_down()) 240 touch_in_progress_ = false; 241 } 242 } 243 244 TouchUMA::GestureActionType TouchUMA::FindGestureActionType( 245 aura::Window* window, 246 const ui::GestureEvent& event) { 247 if (!window || window->GetRootWindow() == window) { 248 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 249 return GESTURE_BEZEL_SCROLL; 250 if (event.type() == ui::ET_GESTURE_BEGIN) 251 return GESTURE_BEZEL_DOWN; 252 return GESTURE_UNKNOWN; 253 } 254 255 std::string name = window ? window->name() : std::string(); 256 257 const char kDesktopBackgroundView[] = "DesktopBackgroundView"; 258 if (name == kDesktopBackgroundView) { 259 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 260 return GESTURE_DESKTOP_SCROLL; 261 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 262 return GESTURE_DESKTOP_PINCH; 263 return GESTURE_UNKNOWN; 264 } 265 266 const char kWebPage[] = "RenderWidgetHostViewAura"; 267 if (name == kWebPage) { 268 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 269 return GESTURE_WEBPAGE_PINCH; 270 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 271 return GESTURE_WEBPAGE_SCROLL; 272 if (event.type() == ui::ET_GESTURE_TAP) 273 return GESTURE_WEBPAGE_TAP; 274 return GESTURE_UNKNOWN; 275 } 276 277 views::Widget* widget = views::Widget::GetWidgetForNativeView(window); 278 if (!widget) 279 return GESTURE_UNKNOWN; 280 281 views::View* view = widget->GetRootView()-> 282 GetEventHandlerForPoint(event.location()); 283 if (!view) 284 return GESTURE_UNKNOWN; 285 286 name = view->GetClassName(); 287 288 const char kTabStrip[] = "TabStrip"; 289 const char kTab[] = "BrowserTab"; 290 if (name == kTabStrip || name == kTab) { 291 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 292 return GESTURE_TABSTRIP_SCROLL; 293 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 294 return GESTURE_TABSTRIP_PINCH; 295 if (event.type() == ui::ET_GESTURE_TAP) 296 return GESTURE_TABSTRIP_TAP; 297 return GESTURE_UNKNOWN; 298 } 299 300 const char kOmnibox[] = "BrowserOmniboxViewViews"; 301 if (name == kOmnibox) { 302 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 303 return GESTURE_OMNIBOX_SCROLL; 304 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 305 return GESTURE_OMNIBOX_PINCH; 306 return GESTURE_UNKNOWN; 307 } 308 309 return GESTURE_UNKNOWN; 310 } 311 312 } // namespace ash 313