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.
4
5 #include "ash/touch/touch_uma.h"
6
7 #include "ash/metrics/user_metrics_recorder.h"
8 #include "ash/shell.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"
18
19 #if defined(USE_XI2_MT)
20 #include <X11/extensions/XInput2.h>
21 #include <X11/Xlib.h>
22 #endif
23
24 namespace {
25
26 enum UMAEventType {
27 // WARNING: Do not change the numerical values of any of these types.
28 // Do not remove deprecated types - just comment them as deprecated.
29 UMA_ET_UNKNOWN = 0,
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
50 UMA_ET_SCROLL = 21,
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
67 // accordingly.
68 UMA_ET_COUNT
69 };
70
71 struct WindowTouchDetails {
WindowTouchDetails__anon01ae68c50111::WindowTouchDetails72 WindowTouchDetails()
73 : max_distance_from_start_squared_(0) {
74 }
75
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_;
79
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_;
83
84 // The maximum distance the first touch point travelled from its starting
85 // location in pixels.
86 float max_distance_from_start_squared_;
87
88 // Last time-stamp of the last touch-end event.
89 base::TimeDelta last_release_time_;
90
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_;
95 };
96
97 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
98 kWindowTouchDetails,
99 NULL);
100
101
UMAEventTypeFromEvent(const ui::Event & event)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;
126 }
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();
131 if (tap_count == 1)
132 return UMA_ET_GESTURE_TAP;
133 if (tap_count == 2)
134 return UMA_ET_GESTURE_DOUBLE_TAP;
135 if (tap_count == 3)
136 return UMA_ET_GESTURE_TRIPLE_TAP;
137 NOTREACHED() << "Received tap with tapcount " << tap_count;
138 return UMA_ET_UNKNOWN;
139 }
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;
160 }
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;
175 }
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;
182 case ui::ET_SCROLL:
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;
188 default:
189 NOTREACHED();
190 return UMA_ET_UNKNOWN;
191 }
192 }
193
194 }
195
196 namespace ash {
197
198 // static
GetInstance()199 TouchUMA* TouchUMA::GetInstance() {
200 return Singleton<TouchUMA>::get();
201 }
202
RecordGestureEvent(aura::Window * target,const ui::GestureEvent & event)203 void TouchUMA::RecordGestureEvent(aura::Window* target,
204 const ui::GestureEvent& event) {
205 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
206 UMAEventTypeFromEvent(event),
207 UMA_ET_COUNT);
208
209 GestureActionType action = FindGestureActionType(target, event);
210 RecordGestureAction(action);
211
212 if (event.type() == ui::ET_GESTURE_END &&
213 event.details().touch_points() == 2) {
214 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
215 if (!details) {
216 LOG(ERROR) << "Window received gesture events without receiving any touch"
217 " events";
218 return;
219 }
220 details->last_mt_time_ = event.time_stamp();
221 }
222 }
223
RecordGestureAction(GestureActionType action)224 void TouchUMA::RecordGestureAction(GestureActionType action) {
225 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
226 return;
227 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
228 GESTURE_ACTION_COUNT);
229 }
230
RecordTouchEvent(aura::Window * target,const ui::TouchEvent & event)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())),
235 1, 500, 100);
236
237 UpdateTouchState(event);
238
239 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
240 if (!details) {
241 details = new WindowTouchDetails;
242 target->SetProperty(kWindowTouchDetails, details);
243 }
244
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);
252
253 gfx::Point position = event.root_location();
254
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));
268 } else {
269 position = ui::EventLocationFromNative(event.native_event());
270 }
271 #else
272 position = ui::EventLocationFromNative(event.native_event());
273 #endif
274 position = gfx::ToFlooredPoint(
275 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
276 }
277
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())));
280
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);
287
288 if (event.type() == ui::ET_TOUCH_PRESSED) {
289 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
290 UMA_TOUCHSCREEN_TAP_DOWN);
291
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;
296
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());
307 }
308 }
309
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",
318 static_cast<int>(
319 sqrt(details->max_distance_from_start_squared_)), 0, 1500, 50);
320 }
321
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);
326
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,
337 1000,
338 20);
339 }
340 }
341 }
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) {
348 int distance = 0;
349 if (details->last_touch_position_.count(event.touch_id())) {
350 gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
351 distance =
352 std::abs(lastpos.x() - event.x()) + std::abs(lastpos.y() - event.y());
353 }
354
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(),
360 1, 50, 25);
361 }
362
363 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
364
365 details->last_move_time_[event.touch_id()] = event.time_stamp();
366 details->last_touch_position_[event.touch_id()] = event.location();
367
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;
372 }
373 }
374
TouchUMA()375 TouchUMA::TouchUMA()
376 : is_single_finger_gesture_(false),
377 touch_in_progress_(false),
378 burst_length_(0) {
379 }
380
~TouchUMA()381 TouchUMA::~TouchUMA() {
382 }
383
UpdateTouchState(const ui::TouchEvent & event)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)) {
390 if (burst_length_) {
391 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
392 std::min(burst_length_, 100));
393 }
394 burst_length_ = 1;
395 } else {
396 ++burst_length_;
397 }
398 } else {
399 is_single_finger_gesture_ = false;
400 }
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;
406 }
407 }
408
FindGestureActionType(aura::Window * window,const ui::GestureEvent & event)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;
418 }
419
420 std::string name = window ? window->name() : std::string();
421
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;
429 }
430
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;
440 }
441
442 views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
443 if (!widget)
444 return GESTURE_UNKNOWN;
445
446 views::View* view = widget->GetRootView()->
447 GetEventHandlerForPoint(event.location());
448 if (!view)
449 return GESTURE_UNKNOWN;
450
451 name = view->GetClassName();
452
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;
463 }
464
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;
472 }
473
474 return GESTURE_UNKNOWN;
475 }
476
477 } // namespace ash
478