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