Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ash / touch / touch_uma.cc
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   UMA_ET_UNKNOWN,
28   UMA_ET_TOUCH_RELEASED,
29   UMA_ET_TOUCH_PRESSED,
30   UMA_ET_TOUCH_MOVED,
31   UMA_ET_TOUCH_STATIONARY,  // Deprecated. Do not remove.
32   UMA_ET_TOUCH_CANCELLED,
33   UMA_ET_GESTURE_SCROLL_BEGIN,
34   UMA_ET_GESTURE_SCROLL_END,
35   UMA_ET_GESTURE_SCROLL_UPDATE,
36   UMA_ET_GESTURE_TAP,
37   UMA_ET_GESTURE_TAP_DOWN,
38   UMA_ET_GESTURE_BEGIN,
39   UMA_ET_GESTURE_END,
40   UMA_ET_GESTURE_DOUBLE_TAP,
41   UMA_ET_GESTURE_TRIPLE_TAP,
42   UMA_ET_GESTURE_TWO_FINGER_TAP,
43   UMA_ET_GESTURE_PINCH_BEGIN,
44   UMA_ET_GESTURE_PINCH_END,
45   UMA_ET_GESTURE_PINCH_UPDATE,
46   UMA_ET_GESTURE_LONG_PRESS,
47   UMA_ET_GESTURE_MULTIFINGER_SWIPE,
48   UMA_ET_SCROLL,
49   UMA_ET_SCROLL_FLING_START,
50   UMA_ET_SCROLL_FLING_CANCEL,
51   UMA_ET_GESTURE_MULTIFINGER_SWIPE_3,
52   UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P,  // 4+ fingers
53   UMA_ET_GESTURE_SCROLL_UPDATE_2,
54   UMA_ET_GESTURE_SCROLL_UPDATE_3,
55   UMA_ET_GESTURE_SCROLL_UPDATE_4P,
56   UMA_ET_GESTURE_PINCH_UPDATE_3,
57   UMA_ET_GESTURE_PINCH_UPDATE_4P,
58   UMA_ET_GESTURE_LONG_TAP,
59   UMA_ET_GESTURE_SHOW_PRESS,
60   UMA_ET_GESTURE_TAP_CANCEL,
61   UMA_ET_GESTURE_WIN8_EDGE_SWIPE,
62   // NOTE: Add new event types only immediately above this line. Make sure to
63   // update the UIEventType enum in tools/metrics/histograms/histograms.xml
64   // accordingly.
65   UMA_ET_COUNT
66 };
67
68 struct WindowTouchDetails {
69   WindowTouchDetails()
70       : max_distance_from_start_squared_(0) {
71   }
72
73   // Move and start times of the touch points. The key is the touch-id.
74   std::map<int, base::TimeDelta> last_move_time_;
75   std::map<int, base::TimeDelta> last_start_time_;
76
77   // The first and last positions of the touch points.
78   std::map<int, gfx::Point> start_touch_position_;
79   std::map<int, gfx::Point> last_touch_position_;
80
81   // The maximum distance the first touch point travelled from its starting
82   // location in pixels.
83   float max_distance_from_start_squared_;
84
85   // Last time-stamp of the last touch-end event.
86   base::TimeDelta last_release_time_;
87
88   // Stores the time of the last touch released on this window (if there was a
89   // multi-touch gesture on the window, then this is the release-time of the
90   // last touch on the window).
91   base::TimeDelta last_mt_time_;
92 };
93
94 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
95                                  kWindowTouchDetails,
96                                  NULL);
97
98
99 UMAEventType UMAEventTypeFromEvent(const ui::Event& event) {
100   switch (event.type()) {
101     case ui::ET_TOUCH_RELEASED:
102       return UMA_ET_TOUCH_RELEASED;
103     case ui::ET_TOUCH_PRESSED:
104       return UMA_ET_TOUCH_PRESSED;
105     case ui::ET_TOUCH_MOVED:
106       return UMA_ET_TOUCH_MOVED;
107     case ui::ET_TOUCH_CANCELLED:
108       return UMA_ET_TOUCH_CANCELLED;
109     case ui::ET_GESTURE_SCROLL_BEGIN:
110       return UMA_ET_GESTURE_SCROLL_BEGIN;
111     case ui::ET_GESTURE_SCROLL_END:
112       return UMA_ET_GESTURE_SCROLL_END;
113     case ui::ET_GESTURE_SCROLL_UPDATE: {
114       const ui::GestureEvent& gesture =
115           static_cast<const ui::GestureEvent&>(event);
116       if (gesture.details().touch_points() >= 4)
117         return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
118       else if (gesture.details().touch_points() == 3)
119         return UMA_ET_GESTURE_SCROLL_UPDATE_3;
120       else if (gesture.details().touch_points() == 2)
121         return UMA_ET_GESTURE_SCROLL_UPDATE_2;
122       return UMA_ET_GESTURE_SCROLL_UPDATE;
123     }
124     case ui::ET_GESTURE_TAP: {
125       const ui::GestureEvent& gesture =
126           static_cast<const ui::GestureEvent&>(event);
127       int tap_count = gesture.details().tap_count();
128       if (tap_count == 1)
129         return UMA_ET_GESTURE_TAP;
130       if (tap_count == 2)
131         return UMA_ET_GESTURE_DOUBLE_TAP;
132       if (tap_count == 3)
133         return UMA_ET_GESTURE_TRIPLE_TAP;
134       NOTREACHED() << "Received tap with tapcount " << tap_count;
135       return UMA_ET_UNKNOWN;
136     }
137     case ui::ET_GESTURE_TAP_DOWN:
138       return UMA_ET_GESTURE_TAP_DOWN;
139     case ui::ET_GESTURE_BEGIN:
140       return UMA_ET_GESTURE_BEGIN;
141     case ui::ET_GESTURE_END:
142       return UMA_ET_GESTURE_END;
143     case ui::ET_GESTURE_TWO_FINGER_TAP:
144       return UMA_ET_GESTURE_TWO_FINGER_TAP;
145     case ui::ET_GESTURE_PINCH_BEGIN:
146       return UMA_ET_GESTURE_PINCH_BEGIN;
147     case ui::ET_GESTURE_PINCH_END:
148       return UMA_ET_GESTURE_PINCH_END;
149     case ui::ET_GESTURE_PINCH_UPDATE: {
150       const ui::GestureEvent& gesture =
151           static_cast<const ui::GestureEvent&>(event);
152       if (gesture.details().touch_points() >= 4)
153         return UMA_ET_GESTURE_PINCH_UPDATE_4P;
154       else if (gesture.details().touch_points() == 3)
155         return UMA_ET_GESTURE_PINCH_UPDATE_3;
156       return UMA_ET_GESTURE_PINCH_UPDATE;
157     }
158     case ui::ET_GESTURE_LONG_PRESS:
159       return UMA_ET_GESTURE_LONG_PRESS;
160     case ui::ET_GESTURE_LONG_TAP:
161       return UMA_ET_GESTURE_LONG_TAP;
162     case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
163       const ui::GestureEvent& gesture =
164           static_cast<const ui::GestureEvent&>(event);
165       if (gesture.details().touch_points() >= 4)
166         return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P;
167       else if (gesture.details().touch_points() == 3)
168         return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3;
169       return UMA_ET_GESTURE_MULTIFINGER_SWIPE;
170     }
171     case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
172       return UMA_ET_GESTURE_WIN8_EDGE_SWIPE;
173     case ui::ET_GESTURE_TAP_CANCEL:
174       return UMA_ET_GESTURE_TAP_CANCEL;
175     case ui::ET_GESTURE_SHOW_PRESS:
176       return UMA_ET_GESTURE_SHOW_PRESS;
177     case ui::ET_SCROLL:
178       return UMA_ET_SCROLL;
179     case ui::ET_SCROLL_FLING_START:
180       return UMA_ET_SCROLL_FLING_START;
181     case ui::ET_SCROLL_FLING_CANCEL:
182       return UMA_ET_SCROLL_FLING_CANCEL;
183     default:
184       NOTREACHED();
185       return UMA_ET_UNKNOWN;
186   }
187 }
188
189 }
190
191 namespace ash {
192
193 // static
194 TouchUMA* TouchUMA::GetInstance() {
195   return Singleton<TouchUMA>::get();
196 }
197
198 void TouchUMA::RecordGestureEvent(aura::Window* target,
199                                   const ui::GestureEvent& event) {
200   UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
201                             UMAEventTypeFromEvent(event),
202                             UMA_ET_COUNT);
203
204   GestureActionType action = FindGestureActionType(target, event);
205   RecordGestureAction(action);
206
207   if (event.type() == ui::ET_GESTURE_END &&
208       event.details().touch_points() == 2) {
209     WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
210     if (!details) {
211       LOG(ERROR) << "Window received gesture events without receiving any touch"
212                     " events";
213       return;
214     }
215     details->last_mt_time_ = event.time_stamp();
216   }
217 }
218
219 void TouchUMA::RecordGestureAction(GestureActionType action) {
220   if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
221     return;
222   UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
223                             GESTURE_ACTION_COUNT);
224 }
225
226 void TouchUMA::RecordTouchEvent(aura::Window* target,
227                                 const ui::TouchEvent& event) {
228   UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
229       static_cast<int>(std::max(event.radius_x(), event.radius_y())),
230       1, 500, 100);
231
232   UpdateTouchState(event);
233
234   WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
235   if (!details) {
236     details = new WindowTouchDetails;
237     target->SetProperty(kWindowTouchDetails, details);
238   }
239
240   // Record the location of the touch points.
241   const int kBucketCountForLocation = 100;
242   const gfx::Rect bounds = target->GetRootWindow()->bounds();
243   const int bucket_size_x = std::max(1,
244                                      bounds.width() / kBucketCountForLocation);
245   const int bucket_size_y = std::max(1,
246                                      bounds.height() / kBucketCountForLocation);
247
248   gfx::Point position = event.root_location();
249
250   // Prefer raw event location (when available) over calibrated location.
251   if (event.HasNativeEvent()) {
252 #if defined(USE_XI2_MT)
253     XEvent* xevent = event.native_event();
254     CHECK_EQ(GenericEvent, xevent->type);
255     XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data);
256     if (xievent->evtype == XI_TouchBegin ||
257         xievent->evtype == XI_TouchUpdate ||
258         xievent->evtype == XI_TouchEnd) {
259       XIDeviceEvent* device_event =
260           static_cast<XIDeviceEvent*>(xevent->xcookie.data);
261       position.SetPoint(static_cast<int>(device_event->event_x),
262                         static_cast<int>(device_event->event_y));
263     } else {
264       position = ui::EventLocationFromNative(event.native_event());
265     }
266 #else
267     position = ui::EventLocationFromNative(event.native_event());
268 #endif
269     position = gfx::ToFlooredPoint(
270         gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
271   }
272
273   position.set_x(std::min(bounds.width() - 1, std::max(0, position.x())));
274   position.set_y(std::min(bounds.height() - 1, std::max(0, position.y())));
275
276   UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
277       position.x() / bucket_size_x,
278       0, kBucketCountForLocation, kBucketCountForLocation + 1);
279   UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
280       position.y() / bucket_size_y,
281       0, kBucketCountForLocation, kBucketCountForLocation + 1);
282
283   if (event.type() == ui::ET_TOUCH_PRESSED) {
284     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
285         UMA_TOUCHSCREEN_TAP_DOWN);
286
287     details->last_start_time_[event.touch_id()] = event.time_stamp();
288     details->start_touch_position_[event.touch_id()] = event.root_location();
289     details->last_touch_position_[event.touch_id()] = event.location();
290     details->max_distance_from_start_squared_ = 0;
291
292     if (details->last_release_time_.ToInternalValue()) {
293       // Measuring the interval between a touch-release and the next
294       // touch-start is probably less useful when doing multi-touch (e.g.
295       // gestures, or multi-touch friendly apps). So count this only if the user
296       // hasn't done any multi-touch during the last 30 seconds.
297       base::TimeDelta diff = event.time_stamp() - details->last_mt_time_;
298       if (diff.InSeconds() > 30) {
299         base::TimeDelta gap = event.time_stamp() - details->last_release_time_;
300         UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
301             gap.InMilliseconds());
302       }
303     }
304
305     // Record the number of touch-points currently active for the window.
306     const int kMaxTouchPoints = 10;
307     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
308         details->last_start_time_.size(),
309         1, kMaxTouchPoints, kMaxTouchPoints + 1);
310   } else if (event.type() == ui::ET_TOUCH_RELEASED) {
311     if (is_single_finger_gesture_) {
312       UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMaxDistance",
313           static_cast<int>(
314               sqrt(details->max_distance_from_start_squared_)), 0, 1500, 50);
315     }
316
317     if (details->last_start_time_.count(event.touch_id())) {
318       base::TimeDelta duration = event.time_stamp() -
319                                  details->last_start_time_[event.touch_id()];
320       UMA_HISTOGRAM_TIMES("Ash.TouchDuration2", duration);
321
322       // Look for touches that were [almost] stationary for a long time.
323       const double kLongStationaryTouchDuration = 10;
324       const int kLongStationaryTouchDistanceSquared = 100;
325       if (duration.InSecondsF() > kLongStationaryTouchDuration) {
326         gfx::Vector2d distance = event.root_location() -
327             details->start_touch_position_[event.touch_id()];
328         if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) {
329           UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
330               duration.InSeconds(),
331               kLongStationaryTouchDuration,
332               1000,
333               20);
334         }
335       }
336     }
337     details->last_start_time_.erase(event.touch_id());
338     details->last_move_time_.erase(event.touch_id());
339     details->start_touch_position_.erase(event.touch_id());
340     details->last_touch_position_.erase(event.touch_id());
341     details->last_release_time_ = event.time_stamp();
342   } else if (event.type() == ui::ET_TOUCH_MOVED) {
343     int distance = 0;
344     if (details->last_touch_position_.count(event.touch_id())) {
345       gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
346       distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y());
347     }
348
349     if (details->last_move_time_.count(event.touch_id())) {
350       base::TimeDelta move_delay = event.time_stamp() -
351                                    details->last_move_time_[event.touch_id()];
352       UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
353                                   move_delay.InMilliseconds(),
354                                   1, 50, 25);
355     }
356
357     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
358
359     details->last_move_time_[event.touch_id()] = event.time_stamp();
360     details->last_touch_position_[event.touch_id()] = event.location();
361
362     float cur_dist = (details->start_touch_position_[event.touch_id()] -
363                       event.root_location()).LengthSquared();
364     if (cur_dist > details->max_distance_from_start_squared_)
365       details->max_distance_from_start_squared_ = cur_dist;
366   }
367 }
368
369 TouchUMA::TouchUMA()
370     : is_single_finger_gesture_(false),
371       touch_in_progress_(false),
372       burst_length_(0) {
373 }
374
375 TouchUMA::~TouchUMA() {
376 }
377
378 void TouchUMA::UpdateTouchState(const ui::TouchEvent& event) {
379   if (event.type() == ui::ET_TOUCH_PRESSED) {
380     if (!touch_in_progress_) {
381       is_single_finger_gesture_ = true;
382       base::TimeDelta difference = event.time_stamp() - last_touch_down_time_;
383       if (difference > base::TimeDelta::FromMilliseconds(250)) {
384         if (burst_length_) {
385           UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
386                                    std::min(burst_length_, 100));
387         }
388         burst_length_ = 1;
389       } else {
390         ++burst_length_;
391       }
392     } else {
393       is_single_finger_gesture_ = false;
394     }
395     touch_in_progress_ = true;
396     last_touch_down_time_ = event.time_stamp();
397   } else if (event.type() == ui::ET_TOUCH_RELEASED) {
398     if (!aura::Env::GetInstance()->is_touch_down())
399       touch_in_progress_ = false;
400   }
401 }
402
403 TouchUMA::GestureActionType TouchUMA::FindGestureActionType(
404     aura::Window* window,
405     const ui::GestureEvent& event) {
406   if (!window || window->GetRootWindow() == window) {
407     if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
408       return GESTURE_BEZEL_SCROLL;
409     if (event.type() == ui::ET_GESTURE_BEGIN)
410       return GESTURE_BEZEL_DOWN;
411     return GESTURE_UNKNOWN;
412   }
413
414   std::string name = window ? window->name() : std::string();
415
416   const char kDesktopBackgroundView[] = "DesktopBackgroundView";
417   if (name == kDesktopBackgroundView) {
418     if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
419       return GESTURE_DESKTOP_SCROLL;
420     if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
421       return GESTURE_DESKTOP_PINCH;
422     return GESTURE_UNKNOWN;
423   }
424
425   const char kWebPage[] = "RenderWidgetHostViewAura";
426   if (name == kWebPage) {
427     if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
428       return GESTURE_WEBPAGE_PINCH;
429     if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
430       return GESTURE_WEBPAGE_SCROLL;
431     if (event.type() == ui::ET_GESTURE_TAP)
432       return GESTURE_WEBPAGE_TAP;
433     return GESTURE_UNKNOWN;
434   }
435
436   views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
437   if (!widget)
438     return GESTURE_UNKNOWN;
439
440   views::View* view = widget->GetRootView()->
441       GetEventHandlerForPoint(event.location());
442   if (!view)
443     return GESTURE_UNKNOWN;
444
445   name = view->GetClassName();
446
447   const char kTabStrip[] = "TabStrip";
448   const char kTab[] = "BrowserTab";
449   if (name == kTabStrip || name == kTab) {
450     if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
451       return GESTURE_TABSTRIP_SCROLL;
452     if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
453       return GESTURE_TABSTRIP_PINCH;
454     if (event.type() == ui::ET_GESTURE_TAP)
455       return GESTURE_TABSTRIP_TAP;
456     return GESTURE_UNKNOWN;
457   }
458
459   const char kOmnibox[] = "BrowserOmniboxViewViews";
460   if (name == kOmnibox) {
461     if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
462       return GESTURE_OMNIBOX_SCROLL;
463     if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
464       return GESTURE_OMNIBOX_PINCH;
465     return GESTURE_UNKNOWN;
466   }
467
468   return GESTURE_UNKNOWN;
469 }
470
471 }  // namespace ash