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