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.
5 #include "ash/touch/touch_uma.h"
7 #include "ash/metrics/user_metrics_recorder.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"
19 #if defined(USE_XI2_MT)
20 #include <X11/extensions/XInput2.h>
28 UMA_ET_TOUCH_RELEASED,
31 UMA_ET_TOUCH_STATIONARY, // Deprecated. Do not remove.
32 UMA_ET_TOUCH_CANCELLED,
33 UMA_ET_GESTURE_SCROLL_BEGIN,
34 UMA_ET_GESTURE_SCROLL_END,
35 UMA_ET_GESTURE_SCROLL_UPDATE,
37 UMA_ET_GESTURE_TAP_DOWN,
40 UMA_ET_GESTURE_DOUBLE_TAP,
41 UMA_ET_GESTURE_TRIPLE_TAP,
42 UMA_ET_GESTURE_TWO_FINGER_TAP,
43 UMA_ET_GESTURE_PINCH_BEGIN,
44 UMA_ET_GESTURE_PINCH_END,
45 UMA_ET_GESTURE_PINCH_UPDATE,
46 UMA_ET_GESTURE_LONG_PRESS,
47 UMA_ET_GESTURE_MULTIFINGER_SWIPE,
49 UMA_ET_SCROLL_FLING_START,
50 UMA_ET_SCROLL_FLING_CANCEL,
51 UMA_ET_GESTURE_MULTIFINGER_SWIPE_3,
52 UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P, // 4+ fingers
53 UMA_ET_GESTURE_SCROLL_UPDATE_2,
54 UMA_ET_GESTURE_SCROLL_UPDATE_3,
55 UMA_ET_GESTURE_SCROLL_UPDATE_4P,
56 UMA_ET_GESTURE_PINCH_UPDATE_3,
57 UMA_ET_GESTURE_PINCH_UPDATE_4P,
58 UMA_ET_GESTURE_LONG_TAP,
59 UMA_ET_GESTURE_SHOW_PRESS,
60 UMA_ET_GESTURE_TAP_CANCEL,
61 UMA_ET_GESTURE_WIN8_EDGE_SWIPE,
62 // NOTE: Add new event types only immediately above this line. Make sure to
63 // update the UIEventType enum in tools/metrics/histograms/histograms.xml
68 struct WindowTouchDetails {
70 : max_distance_from_start_squared_(0) {
73 // Move and start times of the touch points. The key is the touch-id.
74 std::map<int, base::TimeDelta> last_move_time_;
75 std::map<int, base::TimeDelta> last_start_time_;
77 // The first and last positions of the touch points.
78 std::map<int, gfx::Point> start_touch_position_;
79 std::map<int, gfx::Point> last_touch_position_;
81 // The maximum distance the first touch point travelled from its starting
82 // location in pixels.
83 float max_distance_from_start_squared_;
85 // Last time-stamp of the last touch-end event.
86 base::TimeDelta last_release_time_;
88 // Stores the time of the last touch released on this window (if there was a
89 // multi-touch gesture on the window, then this is the release-time of the
90 // last touch on the window).
91 base::TimeDelta last_mt_time_;
94 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
99 UMAEventType UMAEventTypeFromEvent(const ui::Event& event) {
100 switch (event.type()) {
101 case ui::ET_TOUCH_RELEASED:
102 return UMA_ET_TOUCH_RELEASED;
103 case ui::ET_TOUCH_PRESSED:
104 return UMA_ET_TOUCH_PRESSED;
105 case ui::ET_TOUCH_MOVED:
106 return UMA_ET_TOUCH_MOVED;
107 case ui::ET_TOUCH_CANCELLED:
108 return UMA_ET_TOUCH_CANCELLED;
109 case ui::ET_GESTURE_SCROLL_BEGIN:
110 return UMA_ET_GESTURE_SCROLL_BEGIN;
111 case ui::ET_GESTURE_SCROLL_END:
112 return UMA_ET_GESTURE_SCROLL_END;
113 case ui::ET_GESTURE_SCROLL_UPDATE: {
114 const ui::GestureEvent& gesture =
115 static_cast<const ui::GestureEvent&>(event);
116 if (gesture.details().touch_points() >= 4)
117 return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
118 else if (gesture.details().touch_points() == 3)
119 return UMA_ET_GESTURE_SCROLL_UPDATE_3;
120 else if (gesture.details().touch_points() == 2)
121 return UMA_ET_GESTURE_SCROLL_UPDATE_2;
122 return UMA_ET_GESTURE_SCROLL_UPDATE;
124 case ui::ET_GESTURE_TAP: {
125 const ui::GestureEvent& gesture =
126 static_cast<const ui::GestureEvent&>(event);
127 int tap_count = gesture.details().tap_count();
129 return UMA_ET_GESTURE_TAP;
131 return UMA_ET_GESTURE_DOUBLE_TAP;
133 return UMA_ET_GESTURE_TRIPLE_TAP;
134 NOTREACHED() << "Received tap with tapcount " << tap_count;
135 return UMA_ET_UNKNOWN;
137 case ui::ET_GESTURE_TAP_DOWN:
138 return UMA_ET_GESTURE_TAP_DOWN;
139 case ui::ET_GESTURE_BEGIN:
140 return UMA_ET_GESTURE_BEGIN;
141 case ui::ET_GESTURE_END:
142 return UMA_ET_GESTURE_END;
143 case ui::ET_GESTURE_TWO_FINGER_TAP:
144 return UMA_ET_GESTURE_TWO_FINGER_TAP;
145 case ui::ET_GESTURE_PINCH_BEGIN:
146 return UMA_ET_GESTURE_PINCH_BEGIN;
147 case ui::ET_GESTURE_PINCH_END:
148 return UMA_ET_GESTURE_PINCH_END;
149 case ui::ET_GESTURE_PINCH_UPDATE: {
150 const ui::GestureEvent& gesture =
151 static_cast<const ui::GestureEvent&>(event);
152 if (gesture.details().touch_points() >= 4)
153 return UMA_ET_GESTURE_PINCH_UPDATE_4P;
154 else if (gesture.details().touch_points() == 3)
155 return UMA_ET_GESTURE_PINCH_UPDATE_3;
156 return UMA_ET_GESTURE_PINCH_UPDATE;
158 case ui::ET_GESTURE_LONG_PRESS:
159 return UMA_ET_GESTURE_LONG_PRESS;
160 case ui::ET_GESTURE_LONG_TAP:
161 return UMA_ET_GESTURE_LONG_TAP;
162 case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
163 const ui::GestureEvent& gesture =
164 static_cast<const ui::GestureEvent&>(event);
165 if (gesture.details().touch_points() >= 4)
166 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P;
167 else if (gesture.details().touch_points() == 3)
168 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3;
169 return UMA_ET_GESTURE_MULTIFINGER_SWIPE;
171 case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
172 return UMA_ET_GESTURE_WIN8_EDGE_SWIPE;
173 case ui::ET_GESTURE_TAP_CANCEL:
174 return UMA_ET_GESTURE_TAP_CANCEL;
175 case ui::ET_GESTURE_SHOW_PRESS:
176 return UMA_ET_GESTURE_SHOW_PRESS;
178 return UMA_ET_SCROLL;
179 case ui::ET_SCROLL_FLING_START:
180 return UMA_ET_SCROLL_FLING_START;
181 case ui::ET_SCROLL_FLING_CANCEL:
182 return UMA_ET_SCROLL_FLING_CANCEL;
185 return UMA_ET_UNKNOWN;
194 TouchUMA* TouchUMA::GetInstance() {
195 return Singleton<TouchUMA>::get();
198 void TouchUMA::RecordGestureEvent(aura::Window* target,
199 const ui::GestureEvent& event) {
200 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
201 UMAEventTypeFromEvent(event),
204 GestureActionType action = FindGestureActionType(target, event);
205 RecordGestureAction(action);
207 if (event.type() == ui::ET_GESTURE_END &&
208 event.details().touch_points() == 2) {
209 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
211 LOG(ERROR) << "Window received gesture events without receiving any touch"
215 details->last_mt_time_ = event.time_stamp();
219 void TouchUMA::RecordGestureAction(GestureActionType action) {
220 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
222 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
223 GESTURE_ACTION_COUNT);
226 void TouchUMA::RecordTouchEvent(aura::Window* target,
227 const ui::TouchEvent& event) {
228 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
229 static_cast<int>(std::max(event.radius_x(), event.radius_y())),
232 UpdateTouchState(event);
234 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
236 details = new WindowTouchDetails;
237 target->SetProperty(kWindowTouchDetails, details);
240 // Record the location of the touch points.
241 const int kBucketCountForLocation = 100;
242 const gfx::Rect bounds = target->GetRootWindow()->bounds();
243 const int bucket_size_x = std::max(1,
244 bounds.width() / kBucketCountForLocation);
245 const int bucket_size_y = std::max(1,
246 bounds.height() / kBucketCountForLocation);
248 gfx::Point position = event.root_location();
250 // Prefer raw event location (when available) over calibrated location.
251 if (event.HasNativeEvent()) {
252 #if defined(USE_XI2_MT)
253 XEvent* xevent = event.native_event();
254 CHECK_EQ(GenericEvent, xevent->type);
255 XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data);
256 if (xievent->evtype == XI_TouchBegin ||
257 xievent->evtype == XI_TouchUpdate ||
258 xievent->evtype == XI_TouchEnd) {
259 XIDeviceEvent* device_event =
260 static_cast<XIDeviceEvent*>(xevent->xcookie.data);
261 position.SetPoint(static_cast<int>(device_event->event_x),
262 static_cast<int>(device_event->event_y));
264 position = ui::EventLocationFromNative(event.native_event());
267 position = ui::EventLocationFromNative(event.native_event());
269 position = gfx::ToFlooredPoint(
270 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
273 position.set_x(std::min(bounds.width() - 1, std::max(0, position.x())));
274 position.set_y(std::min(bounds.height() - 1, std::max(0, position.y())));
276 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
277 position.x() / bucket_size_x,
278 0, kBucketCountForLocation, kBucketCountForLocation + 1);
279 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
280 position.y() / bucket_size_y,
281 0, kBucketCountForLocation, kBucketCountForLocation + 1);
283 if (event.type() == ui::ET_TOUCH_PRESSED) {
284 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
285 UMA_TOUCHSCREEN_TAP_DOWN);
287 details->last_start_time_[event.touch_id()] = event.time_stamp();
288 details->start_touch_position_[event.touch_id()] = event.root_location();
289 details->last_touch_position_[event.touch_id()] = event.location();
290 details->max_distance_from_start_squared_ = 0;
292 if (details->last_release_time_.ToInternalValue()) {
293 // Measuring the interval between a touch-release and the next
294 // touch-start is probably less useful when doing multi-touch (e.g.
295 // gestures, or multi-touch friendly apps). So count this only if the user
296 // hasn't done any multi-touch during the last 30 seconds.
297 base::TimeDelta diff = event.time_stamp() - details->last_mt_time_;
298 if (diff.InSeconds() > 30) {
299 base::TimeDelta gap = event.time_stamp() - details->last_release_time_;
300 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
301 gap.InMilliseconds());
305 // Record the number of touch-points currently active for the window.
306 const int kMaxTouchPoints = 10;
307 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
308 details->last_start_time_.size(),
309 1, kMaxTouchPoints, kMaxTouchPoints + 1);
310 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
311 if (is_single_finger_gesture_) {
312 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMaxDistance",
314 sqrt(details->max_distance_from_start_squared_)), 0, 1500, 50);
317 if (details->last_start_time_.count(event.touch_id())) {
318 base::TimeDelta duration = event.time_stamp() -
319 details->last_start_time_[event.touch_id()];
320 UMA_HISTOGRAM_TIMES("Ash.TouchDuration2", duration);
322 // Look for touches that were [almost] stationary for a long time.
323 const double kLongStationaryTouchDuration = 10;
324 const int kLongStationaryTouchDistanceSquared = 100;
325 if (duration.InSecondsF() > kLongStationaryTouchDuration) {
326 gfx::Vector2d distance = event.root_location() -
327 details->start_touch_position_[event.touch_id()];
328 if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) {
329 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
330 duration.InSeconds(),
331 kLongStationaryTouchDuration,
337 details->last_start_time_.erase(event.touch_id());
338 details->last_move_time_.erase(event.touch_id());
339 details->start_touch_position_.erase(event.touch_id());
340 details->last_touch_position_.erase(event.touch_id());
341 details->last_release_time_ = event.time_stamp();
342 } else if (event.type() == ui::ET_TOUCH_MOVED) {
344 if (details->last_touch_position_.count(event.touch_id())) {
345 gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
346 distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y());
349 if (details->last_move_time_.count(event.touch_id())) {
350 base::TimeDelta move_delay = event.time_stamp() -
351 details->last_move_time_[event.touch_id()];
352 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
353 move_delay.InMilliseconds(),
357 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
359 details->last_move_time_[event.touch_id()] = event.time_stamp();
360 details->last_touch_position_[event.touch_id()] = event.location();
362 float cur_dist = (details->start_touch_position_[event.touch_id()] -
363 event.root_location()).LengthSquared();
364 if (cur_dist > details->max_distance_from_start_squared_)
365 details->max_distance_from_start_squared_ = cur_dist;
370 : is_single_finger_gesture_(false),
371 touch_in_progress_(false),
375 TouchUMA::~TouchUMA() {
378 void TouchUMA::UpdateTouchState(const ui::TouchEvent& event) {
379 if (event.type() == ui::ET_TOUCH_PRESSED) {
380 if (!touch_in_progress_) {
381 is_single_finger_gesture_ = true;
382 base::TimeDelta difference = event.time_stamp() - last_touch_down_time_;
383 if (difference > base::TimeDelta::FromMilliseconds(250)) {
385 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
386 std::min(burst_length_, 100));
393 is_single_finger_gesture_ = false;
395 touch_in_progress_ = true;
396 last_touch_down_time_ = event.time_stamp();
397 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
398 if (!aura::Env::GetInstance()->is_touch_down())
399 touch_in_progress_ = false;
403 TouchUMA::GestureActionType TouchUMA::FindGestureActionType(
404 aura::Window* window,
405 const ui::GestureEvent& event) {
406 if (!window || window->GetRootWindow() == window) {
407 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
408 return GESTURE_BEZEL_SCROLL;
409 if (event.type() == ui::ET_GESTURE_BEGIN)
410 return GESTURE_BEZEL_DOWN;
411 return GESTURE_UNKNOWN;
414 std::string name = window ? window->name() : std::string();
416 const char kDesktopBackgroundView[] = "DesktopBackgroundView";
417 if (name == kDesktopBackgroundView) {
418 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
419 return GESTURE_DESKTOP_SCROLL;
420 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
421 return GESTURE_DESKTOP_PINCH;
422 return GESTURE_UNKNOWN;
425 const char kWebPage[] = "RenderWidgetHostViewAura";
426 if (name == kWebPage) {
427 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
428 return GESTURE_WEBPAGE_PINCH;
429 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
430 return GESTURE_WEBPAGE_SCROLL;
431 if (event.type() == ui::ET_GESTURE_TAP)
432 return GESTURE_WEBPAGE_TAP;
433 return GESTURE_UNKNOWN;
436 views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
438 return GESTURE_UNKNOWN;
440 views::View* view = widget->GetRootView()->
441 GetEventHandlerForPoint(event.location());
443 return GESTURE_UNKNOWN;
445 name = view->GetClassName();
447 const char kTabStrip[] = "TabStrip";
448 const char kTab[] = "BrowserTab";
449 if (name == kTabStrip || name == kTab) {
450 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
451 return GESTURE_TABSTRIP_SCROLL;
452 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
453 return GESTURE_TABSTRIP_PINCH;
454 if (event.type() == ui::ET_GESTURE_TAP)
455 return GESTURE_TABSTRIP_TAP;
456 return GESTURE_UNKNOWN;
459 const char kOmnibox[] = "BrowserOmniboxViewViews";
460 if (name == kOmnibox) {
461 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
462 return GESTURE_OMNIBOX_SCROLL;
463 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
464 return GESTURE_OMNIBOX_PINCH;
465 return GESTURE_UNKNOWN;
468 return GESTURE_UNKNOWN;