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>
27 // WARNING: Do not change the numerical values of any of these types.
28 // Do not remove deprecated types - just comment them as deprecated.
30 UMA_ET_TOUCH_RELEASED
= 1,
31 UMA_ET_TOUCH_PRESSED
= 2,
32 UMA_ET_TOUCH_MOVED
= 3,
33 UMA_ET_TOUCH_STATIONARY
= 4, // Deprecated. Do not remove.
34 UMA_ET_TOUCH_CANCELLED
= 5,
35 UMA_ET_GESTURE_SCROLL_BEGIN
= 6,
36 UMA_ET_GESTURE_SCROLL_END
= 7,
37 UMA_ET_GESTURE_SCROLL_UPDATE
= 8,
38 UMA_ET_GESTURE_TAP
= 9,
39 UMA_ET_GESTURE_TAP_DOWN
= 10,
40 UMA_ET_GESTURE_BEGIN
= 11,
41 UMA_ET_GESTURE_END
= 12,
42 UMA_ET_GESTURE_DOUBLE_TAP
= 13,
43 UMA_ET_GESTURE_TRIPLE_TAP
= 14,
44 UMA_ET_GESTURE_TWO_FINGER_TAP
= 15,
45 UMA_ET_GESTURE_PINCH_BEGIN
= 16,
46 UMA_ET_GESTURE_PINCH_END
= 17,
47 UMA_ET_GESTURE_PINCH_UPDATE
= 18,
48 UMA_ET_GESTURE_LONG_PRESS
= 19,
49 UMA_ET_GESTURE_SWIPE_2
= 20, // Swipe with 2 fingers
51 UMA_ET_SCROLL_FLING_START
= 22,
52 UMA_ET_SCROLL_FLING_CANCEL
= 23,
53 UMA_ET_GESTURE_SWIPE_3
= 24, // Swipe with 3 fingers
54 UMA_ET_GESTURE_SWIPE_4P
= 25, // Swipe with 4+ fingers
55 UMA_ET_GESTURE_SCROLL_UPDATE_2
= 26,
56 UMA_ET_GESTURE_SCROLL_UPDATE_3
= 27,
57 UMA_ET_GESTURE_SCROLL_UPDATE_4P
= 28,
58 UMA_ET_GESTURE_PINCH_UPDATE_3
= 29,
59 UMA_ET_GESTURE_PINCH_UPDATE_4P
= 30,
60 UMA_ET_GESTURE_LONG_TAP
= 31,
61 UMA_ET_GESTURE_SHOW_PRESS
= 32,
62 UMA_ET_GESTURE_TAP_CANCEL
= 33,
63 UMA_ET_GESTURE_WIN8_EDGE_SWIPE
= 34,
64 UMA_ET_GESTURE_SWIPE_1
= 35, // Swipe with 1 finger
65 // NOTE: Add new event types only immediately above this line. Make sure to
66 // update the UIEventType enum in tools/metrics/histograms/histograms.xml
71 struct WindowTouchDetails
{
73 : max_distance_from_start_squared_(0) {
76 // Move and start times of the touch points. The key is the touch-id.
77 std::map
<int, base::TimeDelta
> last_move_time_
;
78 std::map
<int, base::TimeDelta
> last_start_time_
;
80 // The first and last positions of the touch points.
81 std::map
<int, gfx::Point
> start_touch_position_
;
82 std::map
<int, gfx::Point
> last_touch_position_
;
84 // The maximum distance the first touch point travelled from its starting
85 // location in pixels.
86 float max_distance_from_start_squared_
;
88 // Last time-stamp of the last touch-end event.
89 base::TimeDelta last_release_time_
;
91 // Stores the time of the last touch released on this window (if there was a
92 // multi-touch gesture on the window, then this is the release-time of the
93 // last touch on the window).
94 base::TimeDelta last_mt_time_
;
97 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails
,
102 UMAEventType
UMAEventTypeFromEvent(const ui::Event
& event
) {
103 switch (event
.type()) {
104 case ui::ET_TOUCH_RELEASED
:
105 return UMA_ET_TOUCH_RELEASED
;
106 case ui::ET_TOUCH_PRESSED
:
107 return UMA_ET_TOUCH_PRESSED
;
108 case ui::ET_TOUCH_MOVED
:
109 return UMA_ET_TOUCH_MOVED
;
110 case ui::ET_TOUCH_CANCELLED
:
111 return UMA_ET_TOUCH_CANCELLED
;
112 case ui::ET_GESTURE_SCROLL_BEGIN
:
113 return UMA_ET_GESTURE_SCROLL_BEGIN
;
114 case ui::ET_GESTURE_SCROLL_END
:
115 return UMA_ET_GESTURE_SCROLL_END
;
116 case ui::ET_GESTURE_SCROLL_UPDATE
: {
117 const ui::GestureEvent
& gesture
=
118 static_cast<const ui::GestureEvent
&>(event
);
119 if (gesture
.details().touch_points() == 1)
120 return UMA_ET_GESTURE_SCROLL_UPDATE
;
121 else if (gesture
.details().touch_points() == 2)
122 return UMA_ET_GESTURE_SCROLL_UPDATE_2
;
123 else if (gesture
.details().touch_points() == 3)
124 return UMA_ET_GESTURE_SCROLL_UPDATE_3
;
125 return UMA_ET_GESTURE_SCROLL_UPDATE_4P
;
127 case ui::ET_GESTURE_TAP
: {
128 const ui::GestureEvent
& gesture
=
129 static_cast<const ui::GestureEvent
&>(event
);
130 int tap_count
= gesture
.details().tap_count();
132 return UMA_ET_GESTURE_TAP
;
134 return UMA_ET_GESTURE_DOUBLE_TAP
;
136 return UMA_ET_GESTURE_TRIPLE_TAP
;
137 NOTREACHED() << "Received tap with tapcount " << tap_count
;
138 return UMA_ET_UNKNOWN
;
140 case ui::ET_GESTURE_TAP_DOWN
:
141 return UMA_ET_GESTURE_TAP_DOWN
;
142 case ui::ET_GESTURE_BEGIN
:
143 return UMA_ET_GESTURE_BEGIN
;
144 case ui::ET_GESTURE_END
:
145 return UMA_ET_GESTURE_END
;
146 case ui::ET_GESTURE_TWO_FINGER_TAP
:
147 return UMA_ET_GESTURE_TWO_FINGER_TAP
;
148 case ui::ET_GESTURE_PINCH_BEGIN
:
149 return UMA_ET_GESTURE_PINCH_BEGIN
;
150 case ui::ET_GESTURE_PINCH_END
:
151 return UMA_ET_GESTURE_PINCH_END
;
152 case ui::ET_GESTURE_PINCH_UPDATE
: {
153 const ui::GestureEvent
& gesture
=
154 static_cast<const ui::GestureEvent
&>(event
);
155 if (gesture
.details().touch_points() >= 4)
156 return UMA_ET_GESTURE_PINCH_UPDATE_4P
;
157 else if (gesture
.details().touch_points() == 3)
158 return UMA_ET_GESTURE_PINCH_UPDATE_3
;
159 return UMA_ET_GESTURE_PINCH_UPDATE
;
161 case ui::ET_GESTURE_LONG_PRESS
:
162 return UMA_ET_GESTURE_LONG_PRESS
;
163 case ui::ET_GESTURE_LONG_TAP
:
164 return UMA_ET_GESTURE_LONG_TAP
;
165 case ui::ET_GESTURE_SWIPE
: {
166 const ui::GestureEvent
& gesture
=
167 static_cast<const ui::GestureEvent
&>(event
);
168 if (gesture
.details().touch_points() == 1)
169 return UMA_ET_GESTURE_SWIPE_1
;
170 else if (gesture
.details().touch_points() == 2)
171 return UMA_ET_GESTURE_SWIPE_2
;
172 else if (gesture
.details().touch_points() == 3)
173 return UMA_ET_GESTURE_SWIPE_3
;
174 return UMA_ET_GESTURE_SWIPE_4P
;
176 case ui::ET_GESTURE_WIN8_EDGE_SWIPE
:
177 return UMA_ET_GESTURE_WIN8_EDGE_SWIPE
;
178 case ui::ET_GESTURE_TAP_CANCEL
:
179 return UMA_ET_GESTURE_TAP_CANCEL
;
180 case ui::ET_GESTURE_SHOW_PRESS
:
181 return UMA_ET_GESTURE_SHOW_PRESS
;
183 return UMA_ET_SCROLL
;
184 case ui::ET_SCROLL_FLING_START
:
185 return UMA_ET_SCROLL_FLING_START
;
186 case ui::ET_SCROLL_FLING_CANCEL
:
187 return UMA_ET_SCROLL_FLING_CANCEL
;
190 return UMA_ET_UNKNOWN
;
199 TouchUMA
* TouchUMA::GetInstance() {
200 return Singleton
<TouchUMA
>::get();
203 void TouchUMA::RecordGestureEvent(aura::Window
* target
,
204 const ui::GestureEvent
& event
) {
205 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
206 UMAEventTypeFromEvent(event
),
209 GestureActionType action
= FindGestureActionType(target
, event
);
210 RecordGestureAction(action
);
212 if (event
.type() == ui::ET_GESTURE_END
&&
213 event
.details().touch_points() == 2) {
214 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
216 LOG(ERROR
) << "Window received gesture events without receiving any touch"
220 details
->last_mt_time_
= event
.time_stamp();
224 void TouchUMA::RecordGestureAction(GestureActionType action
) {
225 if (action
== GESTURE_UNKNOWN
|| action
>= GESTURE_ACTION_COUNT
)
227 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action
,
228 GESTURE_ACTION_COUNT
);
231 void TouchUMA::RecordTouchEvent(aura::Window
* target
,
232 const ui::TouchEvent
& event
) {
233 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
234 static_cast<int>(std::max(event
.radius_x(), event
.radius_y())),
237 UpdateTouchState(event
);
239 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
241 details
= new WindowTouchDetails
;
242 target
->SetProperty(kWindowTouchDetails
, details
);
245 // Record the location of the touch points.
246 const int kBucketCountForLocation
= 100;
247 const gfx::Rect bounds
= target
->GetRootWindow()->bounds();
248 const int bucket_size_x
= std::max(1,
249 bounds
.width() / kBucketCountForLocation
);
250 const int bucket_size_y
= std::max(1,
251 bounds
.height() / kBucketCountForLocation
);
253 gfx::Point position
= event
.root_location();
255 // Prefer raw event location (when available) over calibrated location.
256 if (event
.HasNativeEvent()) {
257 #if defined(USE_XI2_MT)
258 XEvent
* xevent
= event
.native_event();
259 CHECK_EQ(GenericEvent
, xevent
->type
);
260 XIEvent
* xievent
= static_cast<XIEvent
*>(xevent
->xcookie
.data
);
261 if (xievent
->evtype
== XI_TouchBegin
||
262 xievent
->evtype
== XI_TouchUpdate
||
263 xievent
->evtype
== XI_TouchEnd
) {
264 XIDeviceEvent
* device_event
=
265 static_cast<XIDeviceEvent
*>(xevent
->xcookie
.data
);
266 position
.SetPoint(static_cast<int>(device_event
->event_x
),
267 static_cast<int>(device_event
->event_y
));
269 position
= ui::EventLocationFromNative(event
.native_event());
272 position
= ui::EventLocationFromNative(event
.native_event());
274 position
= gfx::ToFlooredPoint(
275 gfx::ScalePoint(position
, 1. / target
->layer()->device_scale_factor()));
278 position
.set_x(std::min(bounds
.width() - 1, std::max(0, position
.x())));
279 position
.set_y(std::min(bounds
.height() - 1, std::max(0, position
.y())));
281 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
282 position
.x() / bucket_size_x
,
283 0, kBucketCountForLocation
, kBucketCountForLocation
+ 1);
284 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
285 position
.y() / bucket_size_y
,
286 0, kBucketCountForLocation
, kBucketCountForLocation
+ 1);
288 if (event
.type() == ui::ET_TOUCH_PRESSED
) {
289 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
290 UMA_TOUCHSCREEN_TAP_DOWN
);
292 details
->last_start_time_
[event
.touch_id()] = event
.time_stamp();
293 details
->start_touch_position_
[event
.touch_id()] = event
.root_location();
294 details
->last_touch_position_
[event
.touch_id()] = event
.location();
295 details
->max_distance_from_start_squared_
= 0;
297 if (details
->last_release_time_
.ToInternalValue()) {
298 // Measuring the interval between a touch-release and the next
299 // touch-start is probably less useful when doing multi-touch (e.g.
300 // gestures, or multi-touch friendly apps). So count this only if the user
301 // hasn't done any multi-touch during the last 30 seconds.
302 base::TimeDelta diff
= event
.time_stamp() - details
->last_mt_time_
;
303 if (diff
.InSeconds() > 30) {
304 base::TimeDelta gap
= event
.time_stamp() - details
->last_release_time_
;
305 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
306 gap
.InMilliseconds());
310 // Record the number of touch-points currently active for the window.
311 const int kMaxTouchPoints
= 10;
312 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
313 details
->last_start_time_
.size(),
314 1, kMaxTouchPoints
, kMaxTouchPoints
+ 1);
315 } else if (event
.type() == ui::ET_TOUCH_RELEASED
) {
316 if (is_single_finger_gesture_
) {
317 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMaxDistance",
319 sqrt(details
->max_distance_from_start_squared_
)), 0, 1500, 50);
322 if (details
->last_start_time_
.count(event
.touch_id())) {
323 base::TimeDelta duration
= event
.time_stamp() -
324 details
->last_start_time_
[event
.touch_id()];
325 UMA_HISTOGRAM_TIMES("Ash.TouchDuration2", duration
);
327 // Look for touches that were [almost] stationary for a long time.
328 const double kLongStationaryTouchDuration
= 10;
329 const int kLongStationaryTouchDistanceSquared
= 100;
330 if (duration
.InSecondsF() > kLongStationaryTouchDuration
) {
331 gfx::Vector2d distance
= event
.root_location() -
332 details
->start_touch_position_
[event
.touch_id()];
333 if (distance
.LengthSquared() < kLongStationaryTouchDistanceSquared
) {
334 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
335 duration
.InSeconds(),
336 kLongStationaryTouchDuration
,
342 details
->last_start_time_
.erase(event
.touch_id());
343 details
->last_move_time_
.erase(event
.touch_id());
344 details
->start_touch_position_
.erase(event
.touch_id());
345 details
->last_touch_position_
.erase(event
.touch_id());
346 details
->last_release_time_
= event
.time_stamp();
347 } else if (event
.type() == ui::ET_TOUCH_MOVED
) {
349 if (details
->last_touch_position_
.count(event
.touch_id())) {
350 gfx::Point lastpos
= details
->last_touch_position_
[event
.touch_id()];
352 std::abs(lastpos
.x() - event
.x()) + std::abs(lastpos
.y() - event
.y());
355 if (details
->last_move_time_
.count(event
.touch_id())) {
356 base::TimeDelta move_delay
= event
.time_stamp() -
357 details
->last_move_time_
[event
.touch_id()];
358 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
359 move_delay
.InMilliseconds(),
363 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance
, 1, 1000, 50);
365 details
->last_move_time_
[event
.touch_id()] = event
.time_stamp();
366 details
->last_touch_position_
[event
.touch_id()] = event
.location();
368 float cur_dist
= (details
->start_touch_position_
[event
.touch_id()] -
369 event
.root_location()).LengthSquared();
370 if (cur_dist
> details
->max_distance_from_start_squared_
)
371 details
->max_distance_from_start_squared_
= cur_dist
;
376 : is_single_finger_gesture_(false),
377 touch_in_progress_(false),
381 TouchUMA::~TouchUMA() {
384 void TouchUMA::UpdateTouchState(const ui::TouchEvent
& event
) {
385 if (event
.type() == ui::ET_TOUCH_PRESSED
) {
386 if (!touch_in_progress_
) {
387 is_single_finger_gesture_
= true;
388 base::TimeDelta difference
= event
.time_stamp() - last_touch_down_time_
;
389 if (difference
> base::TimeDelta::FromMilliseconds(250)) {
391 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
392 std::min(burst_length_
, 100));
399 is_single_finger_gesture_
= false;
401 touch_in_progress_
= true;
402 last_touch_down_time_
= event
.time_stamp();
403 } else if (event
.type() == ui::ET_TOUCH_RELEASED
) {
404 if (!aura::Env::GetInstance()->is_touch_down())
405 touch_in_progress_
= false;
409 TouchUMA::GestureActionType
TouchUMA::FindGestureActionType(
410 aura::Window
* window
,
411 const ui::GestureEvent
& event
) {
412 if (!window
|| window
->GetRootWindow() == window
) {
413 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
414 return GESTURE_BEZEL_SCROLL
;
415 if (event
.type() == ui::ET_GESTURE_BEGIN
)
416 return GESTURE_BEZEL_DOWN
;
417 return GESTURE_UNKNOWN
;
420 std::string name
= window
? window
->name() : std::string();
422 const char kDesktopBackgroundView
[] = "DesktopBackgroundView";
423 if (name
== kDesktopBackgroundView
) {
424 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
425 return GESTURE_DESKTOP_SCROLL
;
426 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
427 return GESTURE_DESKTOP_PINCH
;
428 return GESTURE_UNKNOWN
;
431 const char kWebPage
[] = "RenderWidgetHostViewAura";
432 if (name
== kWebPage
) {
433 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
434 return GESTURE_WEBPAGE_PINCH
;
435 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
436 return GESTURE_WEBPAGE_SCROLL
;
437 if (event
.type() == ui::ET_GESTURE_TAP
)
438 return GESTURE_WEBPAGE_TAP
;
439 return GESTURE_UNKNOWN
;
442 views::Widget
* widget
= views::Widget::GetWidgetForNativeView(window
);
444 return GESTURE_UNKNOWN
;
446 views::View
* view
= widget
->GetRootView()->
447 GetEventHandlerForPoint(event
.location());
449 return GESTURE_UNKNOWN
;
451 name
= view
->GetClassName();
453 const char kTabStrip
[] = "TabStrip";
454 const char kTab
[] = "BrowserTab";
455 if (name
== kTabStrip
|| name
== kTab
) {
456 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
457 return GESTURE_TABSTRIP_SCROLL
;
458 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
459 return GESTURE_TABSTRIP_PINCH
;
460 if (event
.type() == ui::ET_GESTURE_TAP
)
461 return GESTURE_TABSTRIP_TAP
;
462 return GESTURE_UNKNOWN
;
465 const char kOmnibox
[] = "BrowserOmniboxViewViews";
466 if (name
== kOmnibox
) {
467 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
468 return GESTURE_OMNIBOX_SCROLL
;
469 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
470 return GESTURE_OMNIBOX_PINCH
;
471 return GESTURE_UNKNOWN
;
474 return GESTURE_UNKNOWN
;