Upstream version 10.39.225.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 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_;
30
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_;
34
35   // Last time-stamp of the last touch-end event.
36   base::TimeDelta last_release_time_;
37
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_;
42 };
43
44 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
45                                  kWindowTouchDetails,
46                                  NULL);
47 }
48
49 namespace ash {
50
51 // static
52 TouchUMA* TouchUMA::GetInstance() {
53   return Singleton<TouchUMA>::get();
54 }
55
56 void TouchUMA::RecordGestureEvent(aura::Window* target,
57                                   const ui::GestureEvent& event) {
58   GestureActionType action = FindGestureActionType(target, event);
59   RecordGestureAction(action);
60
61   if (event.type() == ui::ET_GESTURE_END &&
62       event.details().touch_points() == 2) {
63     WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
64     if (!details) {
65       LOG(ERROR) << "Window received gesture events without receiving any touch"
66                     " events";
67       return;
68     }
69     details->last_mt_time_ = event.time_stamp();
70   }
71 }
72
73 void TouchUMA::RecordGestureAction(GestureActionType action) {
74   if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
75     return;
76   UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
77                             GESTURE_ACTION_COUNT);
78 }
79
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())),
84       1, 500, 100);
85
86   UpdateTouchState(event);
87
88   WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
89   if (!details) {
90     details = new WindowTouchDetails;
91     target->SetProperty(kWindowTouchDetails, details);
92   }
93
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);
101
102   gfx::Point position = event.root_location();
103
104   // Prefer raw event location (when available) over calibrated location.
105   if (event.HasNativeEvent()) {
106 #if defined(USE_XI2_MT)
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));
117     } else {
118       position = ui::EventLocationFromNative(event.native_event());
119     }
120 #else
121     position = ui::EventLocationFromNative(event.native_event());
122 #endif
123     position = gfx::ToFlooredPoint(
124         gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
125   }
126
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())));
129
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);
136
137   if (event.type() == ui::ET_TOUCH_PRESSED) {
138     Shell::GetInstance()->metrics()->RecordUserMetricsAction(
139         UMA_TOUCHSCREEN_TAP_DOWN);
140
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();
144
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());
155       }
156     }
157
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,
177               1000,
178               20);
179         }
180       }
181     }
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) {
188     int distance = 0;
189     if (details->last_touch_position_.count(event.touch_id())) {
190       gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
191       distance =
192           std::abs(lastpos.x() - event.x()) + std::abs(lastpos.y() - event.y());
193     }
194
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(),
200                                   1, 50, 25);
201     }
202
203     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
204
205     details->last_move_time_[event.touch_id()] = event.time_stamp();
206     details->last_touch_position_[event.touch_id()] = event.location();
207   }
208 }
209
210 TouchUMA::TouchUMA()
211     : is_single_finger_gesture_(false),
212       touch_in_progress_(false),
213       burst_length_(0) {
214 }
215
216 TouchUMA::~TouchUMA() {
217 }
218
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)) {
225         if (burst_length_) {
226           UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
227                                    std::min(burst_length_, 100));
228         }
229         burst_length_ = 1;
230       } else {
231         ++burst_length_;
232       }
233     } else {
234       is_single_finger_gesture_ = false;
235     }
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;
241   }
242 }
243
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;
253   }
254
255   std::string name = window ? window->name() : std::string();
256
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;
264   }
265
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;
275   }
276
277   views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
278   if (!widget)
279     return GESTURE_UNKNOWN;
280
281   views::View* view = widget->GetRootView()->
282       GetEventHandlerForPoint(event.location());
283   if (!view)
284     return GESTURE_UNKNOWN;
285
286   name = view->GetClassName();
287
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;
298   }
299
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;
307   }
308
309   return GESTURE_UNKNOWN;
310 }
311
312 }  // namespace ash