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"
20 #include <X11/extensions/XInput2.h>
26 struct WindowTouchDetails
{
27 // Move and start times of the touch points. The key is the touch-id.
28 std::map
<int, base::TimeDelta
> last_move_time_
;
29 std::map
<int, base::TimeDelta
> last_start_time_
;
31 // The first and last positions of the touch points.
32 std::map
<int, gfx::Point
> start_touch_position_
;
33 std::map
<int, gfx::Point
> last_touch_position_
;
35 // Last time-stamp of the last touch-end event.
36 base::TimeDelta last_release_time_
;
38 // Stores the time of the last touch released on this window (if there was a
39 // multi-touch gesture on the window, then this is the release-time of the
40 // last touch on the window).
41 base::TimeDelta last_mt_time_
;
44 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails
,
52 TouchUMA
* TouchUMA::GetInstance() {
53 return Singleton
<TouchUMA
>::get();
56 void TouchUMA::RecordGestureEvent(aura::Window
* target
,
57 const ui::GestureEvent
& event
) {
58 GestureActionType action
= FindGestureActionType(target
, event
);
59 RecordGestureAction(action
);
61 if (event
.type() == ui::ET_GESTURE_END
&&
62 event
.details().touch_points() == 2) {
63 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
65 LOG(ERROR
) << "Window received gesture events without receiving any touch"
69 details
->last_mt_time_
= event
.time_stamp();
73 void TouchUMA::RecordGestureAction(GestureActionType action
) {
74 if (action
== GESTURE_UNKNOWN
|| action
>= GESTURE_ACTION_COUNT
)
76 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action
,
77 GESTURE_ACTION_COUNT
);
80 void TouchUMA::RecordTouchEvent(aura::Window
* target
,
81 const ui::TouchEvent
& event
) {
82 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
83 static_cast<int>(std::max(event
.radius_x(), event
.radius_y())),
86 UpdateTouchState(event
);
88 WindowTouchDetails
* details
= target
->GetProperty(kWindowTouchDetails
);
90 details
= new WindowTouchDetails
;
91 target
->SetProperty(kWindowTouchDetails
, details
);
94 // Record the location of the touch points.
95 const int kBucketCountForLocation
= 100;
96 const gfx::Rect bounds
= target
->GetRootWindow()->bounds();
97 const int bucket_size_x
= std::max(1,
98 bounds
.width() / kBucketCountForLocation
);
99 const int bucket_size_y
= std::max(1,
100 bounds
.height() / kBucketCountForLocation
);
102 gfx::Point position
= event
.root_location();
104 // Prefer raw event location (when available) over calibrated location.
105 if (event
.HasNativeEvent()) {
107 XEvent
* xevent
= event
.native_event();
108 CHECK_EQ(GenericEvent
, xevent
->type
);
109 XIEvent
* xievent
= static_cast<XIEvent
*>(xevent
->xcookie
.data
);
110 if (xievent
->evtype
== XI_TouchBegin
||
111 xievent
->evtype
== XI_TouchUpdate
||
112 xievent
->evtype
== XI_TouchEnd
) {
113 XIDeviceEvent
* device_event
=
114 static_cast<XIDeviceEvent
*>(xevent
->xcookie
.data
);
115 position
.SetPoint(static_cast<int>(device_event
->event_x
),
116 static_cast<int>(device_event
->event_y
));
118 position
= ui::EventLocationFromNative(event
.native_event());
121 position
= ui::EventLocationFromNative(event
.native_event());
123 position
= gfx::ToFlooredPoint(
124 gfx::ScalePoint(position
, 1. / target
->layer()->device_scale_factor()));
127 position
.set_x(std::min(bounds
.width() - 1, std::max(0, position
.x())));
128 position
.set_y(std::min(bounds
.height() - 1, std::max(0, position
.y())));
130 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
131 position
.x() / bucket_size_x
,
132 0, kBucketCountForLocation
, kBucketCountForLocation
+ 1);
133 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
134 position
.y() / bucket_size_y
,
135 0, kBucketCountForLocation
, kBucketCountForLocation
+ 1);
137 if (event
.type() == ui::ET_TOUCH_PRESSED
) {
138 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
139 UMA_TOUCHSCREEN_TAP_DOWN
);
141 details
->last_start_time_
[event
.touch_id()] = event
.time_stamp();
142 details
->start_touch_position_
[event
.touch_id()] = event
.root_location();
143 details
->last_touch_position_
[event
.touch_id()] = event
.location();
145 if (details
->last_release_time_
.ToInternalValue()) {
146 // Measuring the interval between a touch-release and the next
147 // touch-start is probably less useful when doing multi-touch (e.g.
148 // gestures, or multi-touch friendly apps). So count this only if the user
149 // hasn't done any multi-touch during the last 30 seconds.
150 base::TimeDelta diff
= event
.time_stamp() - details
->last_mt_time_
;
151 if (diff
.InSeconds() > 30) {
152 base::TimeDelta gap
= event
.time_stamp() - details
->last_release_time_
;
153 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
154 gap
.InMilliseconds());
158 // Record the number of touch-points currently active for the window.
159 const int kMaxTouchPoints
= 10;
160 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
161 details
->last_start_time_
.size(),
162 1, kMaxTouchPoints
, kMaxTouchPoints
+ 1);
163 } else if (event
.type() == ui::ET_TOUCH_RELEASED
) {
164 if (details
->last_start_time_
.count(event
.touch_id())) {
165 base::TimeDelta duration
= event
.time_stamp() -
166 details
->last_start_time_
[event
.touch_id()];
167 // Look for touches that were [almost] stationary for a long time.
168 const double kLongStationaryTouchDuration
= 10;
169 const int kLongStationaryTouchDistanceSquared
= 100;
170 if (duration
.InSecondsF() > kLongStationaryTouchDuration
) {
171 gfx::Vector2d distance
= event
.root_location() -
172 details
->start_touch_position_
[event
.touch_id()];
173 if (distance
.LengthSquared() < kLongStationaryTouchDistanceSquared
) {
174 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
175 duration
.InSeconds(),
176 kLongStationaryTouchDuration
,
182 details
->last_start_time_
.erase(event
.touch_id());
183 details
->last_move_time_
.erase(event
.touch_id());
184 details
->start_touch_position_
.erase(event
.touch_id());
185 details
->last_touch_position_
.erase(event
.touch_id());
186 details
->last_release_time_
= event
.time_stamp();
187 } else if (event
.type() == ui::ET_TOUCH_MOVED
) {
189 if (details
->last_touch_position_
.count(event
.touch_id())) {
190 gfx::Point lastpos
= details
->last_touch_position_
[event
.touch_id()];
192 std::abs(lastpos
.x() - event
.x()) + std::abs(lastpos
.y() - event
.y());
195 if (details
->last_move_time_
.count(event
.touch_id())) {
196 base::TimeDelta move_delay
= event
.time_stamp() -
197 details
->last_move_time_
[event
.touch_id()];
198 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
199 move_delay
.InMilliseconds(),
203 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance
, 1, 1000, 50);
205 details
->last_move_time_
[event
.touch_id()] = event
.time_stamp();
206 details
->last_touch_position_
[event
.touch_id()] = event
.location();
211 : is_single_finger_gesture_(false),
212 touch_in_progress_(false),
216 TouchUMA::~TouchUMA() {
219 void TouchUMA::UpdateTouchState(const ui::TouchEvent
& event
) {
220 if (event
.type() == ui::ET_TOUCH_PRESSED
) {
221 if (!touch_in_progress_
) {
222 is_single_finger_gesture_
= true;
223 base::TimeDelta difference
= event
.time_stamp() - last_touch_down_time_
;
224 if (difference
> base::TimeDelta::FromMilliseconds(250)) {
226 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
227 std::min(burst_length_
, 100));
234 is_single_finger_gesture_
= false;
236 touch_in_progress_
= true;
237 last_touch_down_time_
= event
.time_stamp();
238 } else if (event
.type() == ui::ET_TOUCH_RELEASED
) {
239 if (!aura::Env::GetInstance()->is_touch_down())
240 touch_in_progress_
= false;
244 TouchUMA::GestureActionType
TouchUMA::FindGestureActionType(
245 aura::Window
* window
,
246 const ui::GestureEvent
& event
) {
247 if (!window
|| window
->GetRootWindow() == window
) {
248 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
249 return GESTURE_BEZEL_SCROLL
;
250 if (event
.type() == ui::ET_GESTURE_BEGIN
)
251 return GESTURE_BEZEL_DOWN
;
252 return GESTURE_UNKNOWN
;
255 std::string name
= window
? window
->name() : std::string();
257 const char kDesktopBackgroundView
[] = "DesktopBackgroundView";
258 if (name
== kDesktopBackgroundView
) {
259 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
260 return GESTURE_DESKTOP_SCROLL
;
261 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
262 return GESTURE_DESKTOP_PINCH
;
263 return GESTURE_UNKNOWN
;
266 const char kWebPage
[] = "RenderWidgetHostViewAura";
267 if (name
== kWebPage
) {
268 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
269 return GESTURE_WEBPAGE_PINCH
;
270 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
271 return GESTURE_WEBPAGE_SCROLL
;
272 if (event
.type() == ui::ET_GESTURE_TAP
)
273 return GESTURE_WEBPAGE_TAP
;
274 return GESTURE_UNKNOWN
;
277 views::Widget
* widget
= views::Widget::GetWidgetForNativeView(window
);
279 return GESTURE_UNKNOWN
;
281 views::View
* view
= widget
->GetRootView()->
282 GetEventHandlerForPoint(event
.location());
284 return GESTURE_UNKNOWN
;
286 name
= view
->GetClassName();
288 const char kTabStrip
[] = "TabStrip";
289 const char kTab
[] = "BrowserTab";
290 if (name
== kTabStrip
|| name
== kTab
) {
291 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
292 return GESTURE_TABSTRIP_SCROLL
;
293 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
294 return GESTURE_TABSTRIP_PINCH
;
295 if (event
.type() == ui::ET_GESTURE_TAP
)
296 return GESTURE_TABSTRIP_TAP
;
297 return GESTURE_UNKNOWN
;
300 const char kOmnibox
[] = "BrowserOmniboxViewViews";
301 if (name
== kOmnibox
) {
302 if (event
.type() == ui::ET_GESTURE_SCROLL_BEGIN
)
303 return GESTURE_OMNIBOX_SCROLL
;
304 if (event
.type() == ui::ET_GESTURE_PINCH_BEGIN
)
305 return GESTURE_OMNIBOX_PINCH
;
306 return GESTURE_UNKNOWN
;
309 return GESTURE_UNKNOWN
;