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/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/events/event.h"
15 #include "ui/events/event_utils.h"
16 #include "ui/gfx/point_conversions.h"
18 #if defined(USE_XI2_MT)
19 #include <X11/extensions/XInput2.h>
27 UMA_ET_TOUCH_RELEASED
,
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
,
36 UMA_ET_GESTURE_TAP_DOWN
,
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
,
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.
63 struct 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_
;
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_
;
72 // Last time-stamp of the last touch-end event.
73 base::TimeDelta last_release_time_
;
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_
;
81 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails
,
86 UMAEventType
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
;
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();
118 return UMA_ET_GESTURE_TAP
;
120 return UMA_ET_GESTURE_DOUBLE_TAP
;
122 return UMA_ET_GESTURE_TRIPLE_TAP
;
123 NOTREACHED() << "Received tap with tapcount " << tap_count
;
124 return UMA_ET_UNKNOWN
;
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
;
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
;
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
;
167 return UMA_ET_UNKNOWN
;
176 TouchUMA
* TouchUMA::GetInstance() {
177 return Singleton
<TouchUMA
>::get();
180 void TouchUMA::RecordGestureEvent(aura::Window
* target
,
181 const ui::GestureEvent
& event
) {
182 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
183 UMAEventTypeFromEvent(event
),
186 GestureActionType action
= FindGestureActionType(target
, event
);
187 RecordGestureAction(action
);
189 if (event
.type() == ui::ET_GESTURE_END
&&
190 event
.details().touch_points() == 2) {
191 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
193 LOG(ERROR
) << "Window received gesture events without receiving any touch"
197 details
->last_mt_time_
= event
.time_stamp();
201 void TouchUMA::RecordGestureAction(GestureActionType action
) {
202 if (action
== GESTURE_UNKNOWN
|| action
>= GESTURE_ACTION_COUNT
)
204 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action
,
205 GESTURE_ACTION_COUNT
);
208 void 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())),
214 UpdateBurstData(event
);
216 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
218 details
= new WindowTouchDetails
;
219 target
->SetProperty(kWindowTouchDetails
, details
);
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
);
230 gfx::Point position
= event
.root_location();
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
));
246 position
= ui::EventLocationFromNative(event
.native_event());
249 position
= ui::EventLocationFromNative(event
.native_event());
251 position
= gfx::ToFlooredPoint(
252 gfx::ScalePoint(position
, 1. / target
->layer()->device_scale_factor()));
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())));
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);
265 if (event
.type() == ui::ET_TOUCH_PRESSED
) {
266 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
267 UMA_TOUCHSCREEN_TAP_DOWN
);
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();
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());
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());
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
,
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
) {
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());
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(),
332 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance
, 1, 1000, 50);
334 details
->last_move_time_
[event
.touch_id()] = event
.time_stamp();
335 details
->last_touch_position_
[event
.touch_id()] = event
.location();
340 : touch_in_progress_(false),
344 TouchUMA::~TouchUMA() {
347 void 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)) {
353 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
354 std::min(burst_length_
, 100));
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;
369 TouchUMA::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
;
380 std::string name
= window
? window
->name() : std::string();
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
;
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
;
402 views::Widget
* widget
= views::Widget::GetWidgetForNativeView(window
);
404 return GESTURE_UNKNOWN
;
406 views::View
* view
= widget
->GetRootView()->
407 GetEventHandlerForPoint(event
.location());
409 return GESTURE_UNKNOWN
;
411 name
= view
->GetClassName();
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
;
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
;
434 return GESTURE_UNKNOWN
;