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/shell_delegate.h" 8#include "base/metrics/histogram.h" 9#include "base/strings/stringprintf.h" 10#include "ui/aura/env.h" 11#include "ui/aura/root_window.h" 12#include "ui/aura/window.h" 13#include "ui/aura/window_property.h" 14#include "ui/base/events/event.h" 15#include "ui/base/events/event_utils.h" 16#include "ui/gfx/point_conversions.h" 17 18#if defined(USE_XI2_MT) 19#include <X11/extensions/XInput2.h> 20#include <X11/Xlib.h> 21#endif 22 23namespace { 24 25enum UMAEventType { 26 UMA_ET_UNKNOWN, 27 UMA_ET_TOUCH_RELEASED, 28 UMA_ET_TOUCH_PRESSED, 29 UMA_ET_TOUCH_MOVED, 30 UMA_ET_TOUCH_STATIONARY, 31 UMA_ET_TOUCH_CANCELLED, 32 UMA_ET_GESTURE_SCROLL_BEGIN, 33 UMA_ET_GESTURE_SCROLL_END, 34 UMA_ET_GESTURE_SCROLL_UPDATE, 35 UMA_ET_GESTURE_TAP, 36 UMA_ET_GESTURE_TAP_DOWN, 37 UMA_ET_GESTURE_BEGIN, 38 UMA_ET_GESTURE_END, 39 UMA_ET_GESTURE_DOUBLE_TAP, 40 UMA_ET_GESTURE_TRIPLE_TAP, 41 UMA_ET_GESTURE_TWO_FINGER_TAP, 42 UMA_ET_GESTURE_PINCH_BEGIN, 43 UMA_ET_GESTURE_PINCH_END, 44 UMA_ET_GESTURE_PINCH_UPDATE, 45 UMA_ET_GESTURE_LONG_PRESS, 46 UMA_ET_GESTURE_MULTIFINGER_SWIPE, 47 UMA_ET_SCROLL, 48 UMA_ET_SCROLL_FLING_START, 49 UMA_ET_SCROLL_FLING_CANCEL, 50 UMA_ET_GESTURE_MULTIFINGER_SWIPE_3, 51 UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P, // 4+ fingers 52 UMA_ET_GESTURE_SCROLL_UPDATE_2, 53 UMA_ET_GESTURE_SCROLL_UPDATE_3, 54 UMA_ET_GESTURE_SCROLL_UPDATE_4P, 55 UMA_ET_GESTURE_PINCH_UPDATE_3, 56 UMA_ET_GESTURE_PINCH_UPDATE_4P, 57 UMA_ET_GESTURE_LONG_TAP, 58 // NOTE: Add new event types only immediately above this line. Make sure to 59 // update the enum list in tools/histogram/histograms.xml accordingly. 60 UMA_ET_COUNT 61}; 62 63struct WindowTouchDetails { 64 // Move and start times of the touch points. The key is the touch-id. 65 std::map<int, base::TimeDelta> last_move_time_; 66 std::map<int, base::TimeDelta> last_start_time_; 67 68 // The first and last positions of the touch points. 69 std::map<int, gfx::Point> start_touch_position_; 70 std::map<int, gfx::Point> last_touch_position_; 71 72 // Last time-stamp of the last touch-end event. 73 base::TimeDelta last_release_time_; 74 75 // Stores the time of the last touch released on this window (if there was a 76 // multi-touch gesture on the window, then this is the release-time of the 77 // last touch on the window). 78 base::TimeDelta last_mt_time_; 79}; 80 81DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails, 82 kWindowTouchDetails, 83 NULL); 84 85 86UMAEventType UMAEventTypeFromEvent(const ui::Event& event) { 87 switch (event.type()) { 88 case ui::ET_TOUCH_RELEASED: 89 return UMA_ET_TOUCH_RELEASED; 90 case ui::ET_TOUCH_PRESSED: 91 return UMA_ET_TOUCH_PRESSED; 92 case ui::ET_TOUCH_MOVED: 93 return UMA_ET_TOUCH_MOVED; 94 case ui::ET_TOUCH_STATIONARY: 95 return UMA_ET_TOUCH_STATIONARY; 96 case ui::ET_TOUCH_CANCELLED: 97 return UMA_ET_TOUCH_CANCELLED; 98 case ui::ET_GESTURE_SCROLL_BEGIN: 99 return UMA_ET_GESTURE_SCROLL_BEGIN; 100 case ui::ET_GESTURE_SCROLL_END: 101 return UMA_ET_GESTURE_SCROLL_END; 102 case ui::ET_GESTURE_SCROLL_UPDATE: { 103 const ui::GestureEvent& gesture = 104 static_cast<const ui::GestureEvent&>(event); 105 if (gesture.details().touch_points() >= 4) 106 return UMA_ET_GESTURE_SCROLL_UPDATE_4P; 107 else if (gesture.details().touch_points() == 3) 108 return UMA_ET_GESTURE_SCROLL_UPDATE_3; 109 else if (gesture.details().touch_points() == 2) 110 return UMA_ET_GESTURE_SCROLL_UPDATE_2; 111 return UMA_ET_GESTURE_SCROLL_UPDATE; 112 } 113 case ui::ET_GESTURE_TAP: { 114 const ui::GestureEvent& gesture = 115 static_cast<const ui::GestureEvent&>(event); 116 int tap_count = gesture.details().tap_count(); 117 if (tap_count == 1) 118 return UMA_ET_GESTURE_TAP; 119 if (tap_count == 2) 120 return UMA_ET_GESTURE_DOUBLE_TAP; 121 if (tap_count == 3) 122 return UMA_ET_GESTURE_TRIPLE_TAP; 123 NOTREACHED() << "Received tap with tapcount " << tap_count; 124 return UMA_ET_UNKNOWN; 125 } 126 case ui::ET_GESTURE_TAP_DOWN: 127 return UMA_ET_GESTURE_TAP_DOWN; 128 case ui::ET_GESTURE_BEGIN: 129 return UMA_ET_GESTURE_BEGIN; 130 case ui::ET_GESTURE_END: 131 return UMA_ET_GESTURE_END; 132 case ui::ET_GESTURE_TWO_FINGER_TAP: 133 return UMA_ET_GESTURE_TWO_FINGER_TAP; 134 case ui::ET_GESTURE_PINCH_BEGIN: 135 return UMA_ET_GESTURE_PINCH_BEGIN; 136 case ui::ET_GESTURE_PINCH_END: 137 return UMA_ET_GESTURE_PINCH_END; 138 case ui::ET_GESTURE_PINCH_UPDATE: { 139 const ui::GestureEvent& gesture = 140 static_cast<const ui::GestureEvent&>(event); 141 if (gesture.details().touch_points() >= 4) 142 return UMA_ET_GESTURE_PINCH_UPDATE_4P; 143 else if (gesture.details().touch_points() == 3) 144 return UMA_ET_GESTURE_PINCH_UPDATE_3; 145 return UMA_ET_GESTURE_PINCH_UPDATE; 146 } 147 case ui::ET_GESTURE_LONG_PRESS: 148 return UMA_ET_GESTURE_LONG_PRESS; 149 case ui::ET_GESTURE_LONG_TAP: 150 return UMA_ET_GESTURE_LONG_TAP; 151 case ui::ET_GESTURE_MULTIFINGER_SWIPE: { 152 const ui::GestureEvent& gesture = 153 static_cast<const ui::GestureEvent&>(event); 154 if (gesture.details().touch_points() >= 4) 155 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P; 156 else if (gesture.details().touch_points() == 3) 157 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3; 158 return UMA_ET_GESTURE_MULTIFINGER_SWIPE; 159 } 160 case ui::ET_SCROLL: 161 return UMA_ET_SCROLL; 162 case ui::ET_SCROLL_FLING_START: 163 return UMA_ET_SCROLL_FLING_START; 164 case ui::ET_SCROLL_FLING_CANCEL: 165 return UMA_ET_SCROLL_FLING_CANCEL; 166 default: 167 return UMA_ET_UNKNOWN; 168 } 169} 170 171} 172 173namespace ash { 174 175// static 176TouchUMA* TouchUMA::GetInstance() { 177 return Singleton<TouchUMA>::get(); 178} 179 180void TouchUMA::RecordGestureEvent(aura::Window* target, 181 const ui::GestureEvent& event) { 182 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated", 183 UMAEventTypeFromEvent(event), 184 UMA_ET_COUNT); 185 186 GestureActionType action = FindGestureActionType(target, event); 187 RecordGestureAction(action); 188 189 if (event.type() == ui::ET_GESTURE_END && 190 event.details().touch_points() == 2) { 191 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); 192 if (!details) { 193 LOG(ERROR) << "Window received gesture events without receiving any touch" 194 " events"; 195 return; 196 } 197 details->last_mt_time_ = event.time_stamp(); 198 } 199} 200 201void TouchUMA::RecordGestureAction(GestureActionType action) { 202 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT) 203 return; 204 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action, 205 GESTURE_ACTION_COUNT); 206} 207 208void TouchUMA::RecordTouchEvent(aura::Window* target, 209 const ui::TouchEvent& event) { 210 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius", 211 static_cast<int>(std::max(event.radius_x(), event.radius_y())), 212 1, 500, 100); 213 214 UpdateBurstData(event); 215 216 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); 217 if (!details) { 218 details = new WindowTouchDetails; 219 target->SetProperty(kWindowTouchDetails, details); 220 } 221 222 // Record the location of the touch points. 223 const int kBucketCountForLocation = 100; 224 const gfx::Rect bounds = target->GetRootWindow()->bounds(); 225 const int bucket_size_x = std::max(1, 226 bounds.width() / kBucketCountForLocation); 227 const int bucket_size_y = std::max(1, 228 bounds.height() / kBucketCountForLocation); 229 230 gfx::Point position = event.root_location(); 231 232 // Prefer raw event location (when available) over calibrated location. 233 if (event.HasNativeEvent()) { 234#if defined(USE_XI2_MT) 235 XEvent* xevent = event.native_event(); 236 CHECK_EQ(GenericEvent, xevent->type); 237 XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data); 238 if (xievent->evtype == XI_TouchBegin || 239 xievent->evtype == XI_TouchUpdate || 240 xievent->evtype == XI_TouchEnd) { 241 XIDeviceEvent* device_event = 242 static_cast<XIDeviceEvent*>(xevent->xcookie.data); 243 position.SetPoint(static_cast<int>(device_event->event_x), 244 static_cast<int>(device_event->event_y)); 245 } else { 246 position = ui::EventLocationFromNative(event.native_event()); 247 } 248#else 249 position = ui::EventLocationFromNative(event.native_event()); 250#endif 251 position = gfx::ToFlooredPoint( 252 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor())); 253 } 254 255 position.set_x(std::min(bounds.width() - 1, std::max(0, position.x()))); 256 position.set_y(std::min(bounds.height() - 1, std::max(0, position.y()))); 257 258 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX", 259 position.x() / bucket_size_x, 260 0, kBucketCountForLocation, kBucketCountForLocation + 1); 261 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY", 262 position.y() / bucket_size_y, 263 0, kBucketCountForLocation, kBucketCountForLocation + 1); 264 265 if (event.type() == ui::ET_TOUCH_PRESSED) { 266 Shell::GetInstance()->delegate()->RecordUserMetricsAction( 267 UMA_TOUCHSCREEN_TAP_DOWN); 268 269 details->last_start_time_[event.touch_id()] = event.time_stamp(); 270 details->start_touch_position_[event.touch_id()] = event.root_location(); 271 details->last_touch_position_[event.touch_id()] = event.location(); 272 273 if (details->last_release_time_.ToInternalValue()) { 274 // Measuring the interval between a touch-release and the next 275 // touch-start is probably less useful when doing multi-touch (e.g. 276 // gestures, or multi-touch friendly apps). So count this only if the user 277 // hasn't done any multi-touch during the last 30 seconds. 278 base::TimeDelta diff = event.time_stamp() - details->last_mt_time_; 279 if (diff.InSeconds() > 30) { 280 base::TimeDelta gap = event.time_stamp() - details->last_release_time_; 281 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd", 282 gap.InMilliseconds()); 283 } 284 } 285 286 // Record the number of touch-points currently active for the window. 287 const int kMaxTouchPoints = 10; 288 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints", 289 details->last_start_time_.size(), 290 1, kMaxTouchPoints, kMaxTouchPoints + 1); 291 } else if (event.type() == ui::ET_TOUCH_RELEASED) { 292 if (details->last_start_time_.count(event.touch_id())) { 293 base::TimeDelta duration = event.time_stamp() - 294 details->last_start_time_[event.touch_id()]; 295 UMA_HISTOGRAM_COUNTS_100("Ash.TouchDuration", duration.InMilliseconds()); 296 297 // Look for touches that were [almost] stationary for a long time. 298 const double kLongStationaryTouchDuration = 10; 299 const int kLongStationaryTouchDistanceSquared = 100; 300 if (duration.InSecondsF() > kLongStationaryTouchDuration) { 301 gfx::Vector2d distance = event.root_location() - 302 details->start_touch_position_[event.touch_id()]; 303 if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) { 304 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration", 305 duration.InSeconds(), 306 kLongStationaryTouchDuration, 307 1000, 308 20); 309 } 310 } 311 } 312 details->last_start_time_.erase(event.touch_id()); 313 details->last_move_time_.erase(event.touch_id()); 314 details->start_touch_position_.erase(event.touch_id()); 315 details->last_touch_position_.erase(event.touch_id()); 316 details->last_release_time_ = event.time_stamp(); 317 } else if (event.type() == ui::ET_TOUCH_MOVED) { 318 int distance = 0; 319 if (details->last_touch_position_.count(event.touch_id())) { 320 gfx::Point lastpos = details->last_touch_position_[event.touch_id()]; 321 distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y()); 322 } 323 324 if (details->last_move_time_.count(event.touch_id())) { 325 base::TimeDelta move_delay = event.time_stamp() - 326 details->last_move_time_[event.touch_id()]; 327 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval", 328 move_delay.InMilliseconds(), 329 1, 50, 25); 330 } 331 332 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50); 333 334 details->last_move_time_[event.touch_id()] = event.time_stamp(); 335 details->last_touch_position_[event.touch_id()] = event.location(); 336 } 337} 338 339TouchUMA::TouchUMA() 340 : touch_in_progress_(false), 341 burst_length_(0) { 342} 343 344TouchUMA::~TouchUMA() { 345} 346 347void TouchUMA::UpdateBurstData(const ui::TouchEvent& event) { 348 if (event.type() == ui::ET_TOUCH_PRESSED) { 349 if (!touch_in_progress_) { 350 base::TimeDelta difference = event.time_stamp() - last_touch_down_time_; 351 if (difference > base::TimeDelta::FromMilliseconds(250)) { 352 if (burst_length_) { 353 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst", 354 std::min(burst_length_, 100)); 355 } 356 burst_length_ = 1; 357 } else { 358 ++burst_length_; 359 } 360 } 361 touch_in_progress_ = true; 362 last_touch_down_time_ = event.time_stamp(); 363 } else if (event.type() == ui::ET_TOUCH_RELEASED) { 364 if (!aura::Env::GetInstance()->is_touch_down()) 365 touch_in_progress_ = false; 366 } 367} 368 369TouchUMA::GestureActionType TouchUMA::FindGestureActionType( 370 aura::Window* window, 371 const ui::GestureEvent& event) { 372 if (!window || window->GetRootWindow() == window) { 373 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 374 return GESTURE_BEZEL_SCROLL; 375 if (event.type() == ui::ET_GESTURE_BEGIN) 376 return GESTURE_BEZEL_DOWN; 377 return GESTURE_UNKNOWN; 378 } 379 380 std::string name = window ? window->name() : std::string(); 381 382 const char kDesktopBackgroundView[] = "DesktopBackgroundView"; 383 if (name == kDesktopBackgroundView) { 384 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 385 return GESTURE_DESKTOP_SCROLL; 386 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 387 return GESTURE_DESKTOP_PINCH; 388 return GESTURE_UNKNOWN; 389 } 390 391 const char kWebPage[] = "RenderWidgetHostViewAura"; 392 if (name == kWebPage) { 393 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 394 return GESTURE_WEBPAGE_PINCH; 395 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 396 return GESTURE_WEBPAGE_SCROLL; 397 if (event.type() == ui::ET_GESTURE_TAP) 398 return GESTURE_WEBPAGE_TAP; 399 return GESTURE_UNKNOWN; 400 } 401 402 views::Widget* widget = views::Widget::GetWidgetForNativeView(window); 403 if (!widget) 404 return GESTURE_UNKNOWN; 405 406 views::View* view = widget->GetRootView()-> 407 GetEventHandlerForPoint(event.location()); 408 if (!view) 409 return GESTURE_UNKNOWN; 410 411 name = view->GetClassName(); 412 413 const char kTabStrip[] = "TabStrip"; 414 const char kTab[] = "BrowserTab"; 415 if (name == kTabStrip || name == kTab) { 416 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 417 return GESTURE_TABSTRIP_SCROLL; 418 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 419 return GESTURE_TABSTRIP_PINCH; 420 if (event.type() == ui::ET_GESTURE_TAP) 421 return GESTURE_TABSTRIP_TAP; 422 return GESTURE_UNKNOWN; 423 } 424 425 const char kOmnibox[] = "BrowserOmniboxViewViews"; 426 if (name == kOmnibox) { 427 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) 428 return GESTURE_OMNIBOX_SCROLL; 429 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) 430 return GESTURE_OMNIBOX_PINCH; 431 return GESTURE_UNKNOWN; 432 } 433 434 return GESTURE_UNKNOWN; 435} 436 437} // namespace ash 438