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/root_window.h"
13 #include "ui/aura/window.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,
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_STATIONARY:
108 return UMA_ET_TOUCH_STATIONARY;
109 case ui::ET_TOUCH_CANCELLED:
110 return UMA_ET_TOUCH_CANCELLED;
111 case ui::ET_GESTURE_SCROLL_BEGIN:
112 return UMA_ET_GESTURE_SCROLL_BEGIN;
113 case ui::ET_GESTURE_SCROLL_END:
114 return UMA_ET_GESTURE_SCROLL_END;
115 case ui::ET_GESTURE_SCROLL_UPDATE: {
116 const ui::GestureEvent& gesture =
117 static_cast<const ui::GestureEvent&>(event);
118 if (gesture.details().touch_points() >= 4)
119 return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
120 else if (gesture.details().touch_points() == 3)
121 return UMA_ET_GESTURE_SCROLL_UPDATE_3;
122 else if (gesture.details().touch_points() == 2)
123 return UMA_ET_GESTURE_SCROLL_UPDATE_2;
124 return UMA_ET_GESTURE_SCROLL_UPDATE;
126 case ui::ET_GESTURE_TAP: {
127 const ui::GestureEvent& gesture =
128 static_cast<const ui::GestureEvent&>(event);
129 int tap_count = gesture.details().tap_count();
131 return UMA_ET_GESTURE_TAP;
133 return UMA_ET_GESTURE_DOUBLE_TAP;
135 return UMA_ET_GESTURE_TRIPLE_TAP;
136 NOTREACHED() << "Received tap with tapcount " << tap_count;
137 return UMA_ET_UNKNOWN;
139 case ui::ET_GESTURE_TAP_DOWN:
140 return UMA_ET_GESTURE_TAP_DOWN;
141 case ui::ET_GESTURE_BEGIN:
142 return UMA_ET_GESTURE_BEGIN;
143 case ui::ET_GESTURE_END:
144 return UMA_ET_GESTURE_END;
145 case ui::ET_GESTURE_TWO_FINGER_TAP:
146 return UMA_ET_GESTURE_TWO_FINGER_TAP;
147 case ui::ET_GESTURE_PINCH_BEGIN:
148 return UMA_ET_GESTURE_PINCH_BEGIN;
149 case ui::ET_GESTURE_PINCH_END:
150 return UMA_ET_GESTURE_PINCH_END;
151 case ui::ET_GESTURE_PINCH_UPDATE: {
152 const ui::GestureEvent& gesture =
153 static_cast<const ui::GestureEvent&>(event);
154 if (gesture.details().touch_points() >= 4)
155 return UMA_ET_GESTURE_PINCH_UPDATE_4P;
156 else if (gesture.details().touch_points() == 3)
157 return UMA_ET_GESTURE_PINCH_UPDATE_3;
158 return UMA_ET_GESTURE_PINCH_UPDATE;
160 case ui::ET_GESTURE_LONG_PRESS:
161 return UMA_ET_GESTURE_LONG_PRESS;
162 case ui::ET_GESTURE_LONG_TAP:
163 return UMA_ET_GESTURE_LONG_TAP;
164 case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
165 const ui::GestureEvent& gesture =
166 static_cast<const ui::GestureEvent&>(event);
167 if (gesture.details().touch_points() >= 4)
168 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P;
169 else if (gesture.details().touch_points() == 3)
170 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3;
171 return UMA_ET_GESTURE_MULTIFINGER_SWIPE;
173 case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
174 return UMA_ET_GESTURE_WIN8_EDGE_SWIPE;
175 case ui::ET_GESTURE_TAP_CANCEL:
176 return UMA_ET_GESTURE_TAP_CANCEL;
177 case ui::ET_GESTURE_SHOW_PRESS:
178 return UMA_ET_GESTURE_SHOW_PRESS;
180 return UMA_ET_SCROLL;
181 case ui::ET_SCROLL_FLING_START:
182 return UMA_ET_SCROLL_FLING_START;
183 case ui::ET_SCROLL_FLING_CANCEL:
184 return UMA_ET_SCROLL_FLING_CANCEL;
187 return UMA_ET_UNKNOWN;
196 TouchUMA* TouchUMA::GetInstance() {
197 return Singleton<TouchUMA>::get();
200 void TouchUMA::RecordGestureEvent(aura::Window* target,
201 const ui::GestureEvent& event) {
202 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
203 UMAEventTypeFromEvent(event),
206 GestureActionType action = FindGestureActionType(target, event);
207 RecordGestureAction(action);
209 if (event.type() == ui::ET_GESTURE_END &&
210 event.details().touch_points() == 2) {
211 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
213 LOG(ERROR) << "Window received gesture events without receiving any touch"
217 details->last_mt_time_ = event.time_stamp();
221 void TouchUMA::RecordGestureAction(GestureActionType action) {
222 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
224 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
225 GESTURE_ACTION_COUNT);
228 void TouchUMA::RecordTouchEvent(aura::Window* target,
229 const ui::TouchEvent& event) {
230 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
231 static_cast<int>(std::max(event.radius_x(), event.radius_y())),
234 UpdateTouchState(event);
236 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
238 details = new WindowTouchDetails;
239 target->SetProperty(kWindowTouchDetails, details);
242 // Record the location of the touch points.
243 const int kBucketCountForLocation = 100;
244 const gfx::Rect bounds = target->GetRootWindow()->bounds();
245 const int bucket_size_x = std::max(1,
246 bounds.width() / kBucketCountForLocation);
247 const int bucket_size_y = std::max(1,
248 bounds.height() / kBucketCountForLocation);
250 gfx::Point position = event.root_location();
252 // Prefer raw event location (when available) over calibrated location.
253 if (event.HasNativeEvent()) {
254 #if defined(USE_XI2_MT)
255 XEvent* xevent = event.native_event();
256 CHECK_EQ(GenericEvent, xevent->type);
257 XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data);
258 if (xievent->evtype == XI_TouchBegin ||
259 xievent->evtype == XI_TouchUpdate ||
260 xievent->evtype == XI_TouchEnd) {
261 XIDeviceEvent* device_event =
262 static_cast<XIDeviceEvent*>(xevent->xcookie.data);
263 position.SetPoint(static_cast<int>(device_event->event_x),
264 static_cast<int>(device_event->event_y));
266 position = ui::EventLocationFromNative(event.native_event());
269 position = ui::EventLocationFromNative(event.native_event());
271 position = gfx::ToFlooredPoint(
272 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
275 position.set_x(std::min(bounds.width() - 1, std::max(0, position.x())));
276 position.set_y(std::min(bounds.height() - 1, std::max(0, position.y())));
278 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
279 position.x() / bucket_size_x,
280 0, kBucketCountForLocation, kBucketCountForLocation + 1);
281 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
282 position.y() / bucket_size_y,
283 0, kBucketCountForLocation, kBucketCountForLocation + 1);
285 if (event.type() == ui::ET_TOUCH_PRESSED) {
286 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
287 UMA_TOUCHSCREEN_TAP_DOWN);
289 details->last_start_time_[event.touch_id()] = event.time_stamp();
290 details->start_touch_position_[event.touch_id()] = event.root_location();
291 details->last_touch_position_[event.touch_id()] = event.location();
292 details->max_distance_from_start_squared_ = 0;
294 if (details->last_release_time_.ToInternalValue()) {
295 // Measuring the interval between a touch-release and the next
296 // touch-start is probably less useful when doing multi-touch (e.g.
297 // gestures, or multi-touch friendly apps). So count this only if the user
298 // hasn't done any multi-touch during the last 30 seconds.
299 base::TimeDelta diff = event.time_stamp() - details->last_mt_time_;
300 if (diff.InSeconds() > 30) {
301 base::TimeDelta gap = event.time_stamp() - details->last_release_time_;
302 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
303 gap.InMilliseconds());
307 // Record the number of touch-points currently active for the window.
308 const int kMaxTouchPoints = 10;
309 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
310 details->last_start_time_.size(),
311 1, kMaxTouchPoints, kMaxTouchPoints + 1);
312 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
313 if (is_single_finger_gesture_) {
314 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMaxDistance",
316 sqrt(details->max_distance_from_start_squared_)), 0, 1500, 50);
319 if (details->last_start_time_.count(event.touch_id())) {
320 base::TimeDelta duration = event.time_stamp() -
321 details->last_start_time_[event.touch_id()];
322 UMA_HISTOGRAM_TIMES("Ash.TouchDuration2", duration);
324 // Look for touches that were [almost] stationary for a long time.
325 const double kLongStationaryTouchDuration = 10;
326 const int kLongStationaryTouchDistanceSquared = 100;
327 if (duration.InSecondsF() > kLongStationaryTouchDuration) {
328 gfx::Vector2d distance = event.root_location() -
329 details->start_touch_position_[event.touch_id()];
330 if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) {
331 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
332 duration.InSeconds(),
333 kLongStationaryTouchDuration,
339 details->last_start_time_.erase(event.touch_id());
340 details->last_move_time_.erase(event.touch_id());
341 details->start_touch_position_.erase(event.touch_id());
342 details->last_touch_position_.erase(event.touch_id());
343 details->last_release_time_ = event.time_stamp();
344 } else if (event.type() == ui::ET_TOUCH_MOVED) {
346 if (details->last_touch_position_.count(event.touch_id())) {
347 gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
348 distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y());
351 if (details->last_move_time_.count(event.touch_id())) {
352 base::TimeDelta move_delay = event.time_stamp() -
353 details->last_move_time_[event.touch_id()];
354 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
355 move_delay.InMilliseconds(),
359 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
361 details->last_move_time_[event.touch_id()] = event.time_stamp();
362 details->last_touch_position_[event.touch_id()] = event.location();
364 float cur_dist = (details->start_touch_position_[event.touch_id()] -
365 event.root_location()).LengthSquared();
366 if (cur_dist > details->max_distance_from_start_squared_)
367 details->max_distance_from_start_squared_ = cur_dist;
372 : is_single_finger_gesture_(false),
373 touch_in_progress_(false),
377 TouchUMA::~TouchUMA() {
380 void TouchUMA::UpdateTouchState(const ui::TouchEvent& event) {
381 if (event.type() == ui::ET_TOUCH_PRESSED) {
382 if (!touch_in_progress_) {
383 is_single_finger_gesture_ = true;
384 base::TimeDelta difference = event.time_stamp() - last_touch_down_time_;
385 if (difference > base::TimeDelta::FromMilliseconds(250)) {
387 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
388 std::min(burst_length_, 100));
395 is_single_finger_gesture_ = false;
397 touch_in_progress_ = true;
398 last_touch_down_time_ = event.time_stamp();
399 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
400 if (!aura::Env::GetInstance()->is_touch_down())
401 touch_in_progress_ = false;
405 TouchUMA::GestureActionType TouchUMA::FindGestureActionType(
406 aura::Window* window,
407 const ui::GestureEvent& event) {
408 if (!window || window->GetRootWindow() == window) {
409 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
410 return GESTURE_BEZEL_SCROLL;
411 if (event.type() == ui::ET_GESTURE_BEGIN)
412 return GESTURE_BEZEL_DOWN;
413 return GESTURE_UNKNOWN;
416 std::string name = window ? window->name() : std::string();
418 const char kDesktopBackgroundView[] = "DesktopBackgroundView";
419 if (name == kDesktopBackgroundView) {
420 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
421 return GESTURE_DESKTOP_SCROLL;
422 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
423 return GESTURE_DESKTOP_PINCH;
424 return GESTURE_UNKNOWN;
427 const char kWebPage[] = "RenderWidgetHostViewAura";
428 if (name == kWebPage) {
429 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
430 return GESTURE_WEBPAGE_PINCH;
431 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
432 return GESTURE_WEBPAGE_SCROLL;
433 if (event.type() == ui::ET_GESTURE_TAP)
434 return GESTURE_WEBPAGE_TAP;
435 return GESTURE_UNKNOWN;
438 views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
440 return GESTURE_UNKNOWN;
442 views::View* view = widget->GetRootView()->
443 GetEventHandlerForPoint(event.location());
445 return GESTURE_UNKNOWN;
447 name = view->GetClassName();
449 const char kTabStrip[] = "TabStrip";
450 const char kTab[] = "BrowserTab";
451 if (name == kTabStrip || name == kTab) {
452 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
453 return GESTURE_TABSTRIP_SCROLL;
454 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
455 return GESTURE_TABSTRIP_PINCH;
456 if (event.type() == ui::ET_GESTURE_TAP)
457 return GESTURE_TABSTRIP_TAP;
458 return GESTURE_UNKNOWN;
461 const char kOmnibox[] = "BrowserOmniboxViewViews";
462 if (name == kOmnibox) {
463 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
464 return GESTURE_OMNIBOX_SCROLL;
465 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
466 return GESTURE_OMNIBOX_PINCH;
467 return GESTURE_UNKNOWN;
470 return GESTURE_UNKNOWN;