Make DesktopResizer use ScreenResolution to facilitate DPI-awareness.
[chromium-blink-merge.git] / ash / touch / touch_uma.cc
blob29f61850ed1f97c4e4b4c88f8f6d0e0a7cb237b6
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/shell_delegate.h"
8 #include "base/metrics/histogram.h"
9 #include "base/strings/stringprintf.h"
10 #include "ui/aura/env.h"
11 #include "ui/aura/root_window.h"
12 #include "ui/aura/window.h"
13 #include "ui/aura/window_property.h"
14 #include "ui/events/event.h"
15 #include "ui/events/event_utils.h"
16 #include "ui/gfx/point_conversions.h"
18 #if defined(USE_XI2_MT)
19 #include <X11/extensions/XInput2.h>
20 #include <X11/Xlib.h>
21 #endif
23 namespace {
25 enum UMAEventType {
26 UMA_ET_UNKNOWN,
27 UMA_ET_TOUCH_RELEASED,
28 UMA_ET_TOUCH_PRESSED,
29 UMA_ET_TOUCH_MOVED,
30 UMA_ET_TOUCH_STATIONARY,
31 UMA_ET_TOUCH_CANCELLED,
32 UMA_ET_GESTURE_SCROLL_BEGIN,
33 UMA_ET_GESTURE_SCROLL_END,
34 UMA_ET_GESTURE_SCROLL_UPDATE,
35 UMA_ET_GESTURE_TAP,
36 UMA_ET_GESTURE_TAP_DOWN,
37 UMA_ET_GESTURE_BEGIN,
38 UMA_ET_GESTURE_END,
39 UMA_ET_GESTURE_DOUBLE_TAP,
40 UMA_ET_GESTURE_TRIPLE_TAP,
41 UMA_ET_GESTURE_TWO_FINGER_TAP,
42 UMA_ET_GESTURE_PINCH_BEGIN,
43 UMA_ET_GESTURE_PINCH_END,
44 UMA_ET_GESTURE_PINCH_UPDATE,
45 UMA_ET_GESTURE_LONG_PRESS,
46 UMA_ET_GESTURE_MULTIFINGER_SWIPE,
47 UMA_ET_SCROLL,
48 UMA_ET_SCROLL_FLING_START,
49 UMA_ET_SCROLL_FLING_CANCEL,
50 UMA_ET_GESTURE_MULTIFINGER_SWIPE_3,
51 UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P, // 4+ fingers
52 UMA_ET_GESTURE_SCROLL_UPDATE_2,
53 UMA_ET_GESTURE_SCROLL_UPDATE_3,
54 UMA_ET_GESTURE_SCROLL_UPDATE_4P,
55 UMA_ET_GESTURE_PINCH_UPDATE_3,
56 UMA_ET_GESTURE_PINCH_UPDATE_4P,
57 UMA_ET_GESTURE_LONG_TAP,
58 // NOTE: Add new event types only immediately above this line. Make sure to
59 // update the enum list in tools/histogram/histograms.xml accordingly.
60 UMA_ET_COUNT
63 struct WindowTouchDetails {
64 // Move and start times of the touch points. The key is the touch-id.
65 std::map<int, base::TimeDelta> last_move_time_;
66 std::map<int, base::TimeDelta> last_start_time_;
68 // The first and last positions of the touch points.
69 std::map<int, gfx::Point> start_touch_position_;
70 std::map<int, gfx::Point> last_touch_position_;
72 // Last time-stamp of the last touch-end event.
73 base::TimeDelta last_release_time_;
75 // Stores the time of the last touch released on this window (if there was a
76 // multi-touch gesture on the window, then this is the release-time of the
77 // last touch on the window).
78 base::TimeDelta last_mt_time_;
81 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
82 kWindowTouchDetails,
83 NULL);
86 UMAEventType UMAEventTypeFromEvent(const ui::Event& event) {
87 switch (event.type()) {
88 case ui::ET_TOUCH_RELEASED:
89 return UMA_ET_TOUCH_RELEASED;
90 case ui::ET_TOUCH_PRESSED:
91 return UMA_ET_TOUCH_PRESSED;
92 case ui::ET_TOUCH_MOVED:
93 return UMA_ET_TOUCH_MOVED;
94 case ui::ET_TOUCH_STATIONARY:
95 return UMA_ET_TOUCH_STATIONARY;
96 case ui::ET_TOUCH_CANCELLED:
97 return UMA_ET_TOUCH_CANCELLED;
98 case ui::ET_GESTURE_SCROLL_BEGIN:
99 return UMA_ET_GESTURE_SCROLL_BEGIN;
100 case ui::ET_GESTURE_SCROLL_END:
101 return UMA_ET_GESTURE_SCROLL_END;
102 case ui::ET_GESTURE_SCROLL_UPDATE: {
103 const ui::GestureEvent& gesture =
104 static_cast<const ui::GestureEvent&>(event);
105 if (gesture.details().touch_points() >= 4)
106 return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
107 else if (gesture.details().touch_points() == 3)
108 return UMA_ET_GESTURE_SCROLL_UPDATE_3;
109 else if (gesture.details().touch_points() == 2)
110 return UMA_ET_GESTURE_SCROLL_UPDATE_2;
111 return UMA_ET_GESTURE_SCROLL_UPDATE;
113 case ui::ET_GESTURE_TAP: {
114 const ui::GestureEvent& gesture =
115 static_cast<const ui::GestureEvent&>(event);
116 int tap_count = gesture.details().tap_count();
117 if (tap_count == 1)
118 return UMA_ET_GESTURE_TAP;
119 if (tap_count == 2)
120 return UMA_ET_GESTURE_DOUBLE_TAP;
121 if (tap_count == 3)
122 return UMA_ET_GESTURE_TRIPLE_TAP;
123 NOTREACHED() << "Received tap with tapcount " << tap_count;
124 return UMA_ET_UNKNOWN;
126 case ui::ET_GESTURE_TAP_DOWN:
127 return UMA_ET_GESTURE_TAP_DOWN;
128 case ui::ET_GESTURE_BEGIN:
129 return UMA_ET_GESTURE_BEGIN;
130 case ui::ET_GESTURE_END:
131 return UMA_ET_GESTURE_END;
132 case ui::ET_GESTURE_TWO_FINGER_TAP:
133 return UMA_ET_GESTURE_TWO_FINGER_TAP;
134 case ui::ET_GESTURE_PINCH_BEGIN:
135 return UMA_ET_GESTURE_PINCH_BEGIN;
136 case ui::ET_GESTURE_PINCH_END:
137 return UMA_ET_GESTURE_PINCH_END;
138 case ui::ET_GESTURE_PINCH_UPDATE: {
139 const ui::GestureEvent& gesture =
140 static_cast<const ui::GestureEvent&>(event);
141 if (gesture.details().touch_points() >= 4)
142 return UMA_ET_GESTURE_PINCH_UPDATE_4P;
143 else if (gesture.details().touch_points() == 3)
144 return UMA_ET_GESTURE_PINCH_UPDATE_3;
145 return UMA_ET_GESTURE_PINCH_UPDATE;
147 case ui::ET_GESTURE_LONG_PRESS:
148 return UMA_ET_GESTURE_LONG_PRESS;
149 case ui::ET_GESTURE_LONG_TAP:
150 return UMA_ET_GESTURE_LONG_TAP;
151 case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
152 const ui::GestureEvent& gesture =
153 static_cast<const ui::GestureEvent&>(event);
154 if (gesture.details().touch_points() >= 4)
155 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P;
156 else if (gesture.details().touch_points() == 3)
157 return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3;
158 return UMA_ET_GESTURE_MULTIFINGER_SWIPE;
160 case ui::ET_SCROLL:
161 return UMA_ET_SCROLL;
162 case ui::ET_SCROLL_FLING_START:
163 return UMA_ET_SCROLL_FLING_START;
164 case ui::ET_SCROLL_FLING_CANCEL:
165 return UMA_ET_SCROLL_FLING_CANCEL;
166 default:
167 return UMA_ET_UNKNOWN;
173 namespace ash {
175 // static
176 TouchUMA* TouchUMA::GetInstance() {
177 return Singleton<TouchUMA>::get();
180 void TouchUMA::RecordGestureEvent(aura::Window* target,
181 const ui::GestureEvent& event) {
182 UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
183 UMAEventTypeFromEvent(event),
184 UMA_ET_COUNT);
186 GestureActionType action = FindGestureActionType(target, event);
187 RecordGestureAction(action);
189 if (event.type() == ui::ET_GESTURE_END &&
190 event.details().touch_points() == 2) {
191 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
192 if (!details) {
193 LOG(ERROR) << "Window received gesture events without receiving any touch"
194 " events";
195 return;
197 details->last_mt_time_ = event.time_stamp();
201 void TouchUMA::RecordGestureAction(GestureActionType action) {
202 if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
203 return;
204 UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
205 GESTURE_ACTION_COUNT);
208 void TouchUMA::RecordTouchEvent(aura::Window* target,
209 const ui::TouchEvent& event) {
210 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
211 static_cast<int>(std::max(event.radius_x(), event.radius_y())),
212 1, 500, 100);
214 UpdateBurstData(event);
216 WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
217 if (!details) {
218 details = new WindowTouchDetails;
219 target->SetProperty(kWindowTouchDetails, details);
222 // Record the location of the touch points.
223 const int kBucketCountForLocation = 100;
224 const gfx::Rect bounds = target->GetRootWindow()->bounds();
225 const int bucket_size_x = std::max(1,
226 bounds.width() / kBucketCountForLocation);
227 const int bucket_size_y = std::max(1,
228 bounds.height() / kBucketCountForLocation);
230 gfx::Point position = event.root_location();
232 // Prefer raw event location (when available) over calibrated location.
233 if (event.HasNativeEvent()) {
234 #if defined(USE_XI2_MT)
235 XEvent* xevent = event.native_event();
236 CHECK_EQ(GenericEvent, xevent->type);
237 XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data);
238 if (xievent->evtype == XI_TouchBegin ||
239 xievent->evtype == XI_TouchUpdate ||
240 xievent->evtype == XI_TouchEnd) {
241 XIDeviceEvent* device_event =
242 static_cast<XIDeviceEvent*>(xevent->xcookie.data);
243 position.SetPoint(static_cast<int>(device_event->event_x),
244 static_cast<int>(device_event->event_y));
245 } else {
246 position = ui::EventLocationFromNative(event.native_event());
248 #else
249 position = ui::EventLocationFromNative(event.native_event());
250 #endif
251 position = gfx::ToFlooredPoint(
252 gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
255 position.set_x(std::min(bounds.width() - 1, std::max(0, position.x())));
256 position.set_y(std::min(bounds.height() - 1, std::max(0, position.y())));
258 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
259 position.x() / bucket_size_x,
260 0, kBucketCountForLocation, kBucketCountForLocation + 1);
261 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
262 position.y() / bucket_size_y,
263 0, kBucketCountForLocation, kBucketCountForLocation + 1);
265 if (event.type() == ui::ET_TOUCH_PRESSED) {
266 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
267 UMA_TOUCHSCREEN_TAP_DOWN);
269 details->last_start_time_[event.touch_id()] = event.time_stamp();
270 details->start_touch_position_[event.touch_id()] = event.root_location();
271 details->last_touch_position_[event.touch_id()] = event.location();
273 if (details->last_release_time_.ToInternalValue()) {
274 // Measuring the interval between a touch-release and the next
275 // touch-start is probably less useful when doing multi-touch (e.g.
276 // gestures, or multi-touch friendly apps). So count this only if the user
277 // hasn't done any multi-touch during the last 30 seconds.
278 base::TimeDelta diff = event.time_stamp() - details->last_mt_time_;
279 if (diff.InSeconds() > 30) {
280 base::TimeDelta gap = event.time_stamp() - details->last_release_time_;
281 UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
282 gap.InMilliseconds());
286 // Record the number of touch-points currently active for the window.
287 const int kMaxTouchPoints = 10;
288 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
289 details->last_start_time_.size(),
290 1, kMaxTouchPoints, kMaxTouchPoints + 1);
291 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
292 if (details->last_start_time_.count(event.touch_id())) {
293 base::TimeDelta duration = event.time_stamp() -
294 details->last_start_time_[event.touch_id()];
295 UMA_HISTOGRAM_COUNTS_100("Ash.TouchDuration", duration.InMilliseconds());
297 // Look for touches that were [almost] stationary for a long time.
298 const double kLongStationaryTouchDuration = 10;
299 const int kLongStationaryTouchDistanceSquared = 100;
300 if (duration.InSecondsF() > kLongStationaryTouchDuration) {
301 gfx::Vector2d distance = event.root_location() -
302 details->start_touch_position_[event.touch_id()];
303 if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) {
304 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
305 duration.InSeconds(),
306 kLongStationaryTouchDuration,
307 1000,
308 20);
312 details->last_start_time_.erase(event.touch_id());
313 details->last_move_time_.erase(event.touch_id());
314 details->start_touch_position_.erase(event.touch_id());
315 details->last_touch_position_.erase(event.touch_id());
316 details->last_release_time_ = event.time_stamp();
317 } else if (event.type() == ui::ET_TOUCH_MOVED) {
318 int distance = 0;
319 if (details->last_touch_position_.count(event.touch_id())) {
320 gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
321 distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y());
324 if (details->last_move_time_.count(event.touch_id())) {
325 base::TimeDelta move_delay = event.time_stamp() -
326 details->last_move_time_[event.touch_id()];
327 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
328 move_delay.InMilliseconds(),
329 1, 50, 25);
332 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
334 details->last_move_time_[event.touch_id()] = event.time_stamp();
335 details->last_touch_position_[event.touch_id()] = event.location();
339 TouchUMA::TouchUMA()
340 : touch_in_progress_(false),
341 burst_length_(0) {
344 TouchUMA::~TouchUMA() {
347 void TouchUMA::UpdateBurstData(const ui::TouchEvent& event) {
348 if (event.type() == ui::ET_TOUCH_PRESSED) {
349 if (!touch_in_progress_) {
350 base::TimeDelta difference = event.time_stamp() - last_touch_down_time_;
351 if (difference > base::TimeDelta::FromMilliseconds(250)) {
352 if (burst_length_) {
353 UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
354 std::min(burst_length_, 100));
356 burst_length_ = 1;
357 } else {
358 ++burst_length_;
361 touch_in_progress_ = true;
362 last_touch_down_time_ = event.time_stamp();
363 } else if (event.type() == ui::ET_TOUCH_RELEASED) {
364 if (!aura::Env::GetInstance()->is_touch_down())
365 touch_in_progress_ = false;
369 TouchUMA::GestureActionType TouchUMA::FindGestureActionType(
370 aura::Window* window,
371 const ui::GestureEvent& event) {
372 if (!window || window->GetRootWindow() == window) {
373 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
374 return GESTURE_BEZEL_SCROLL;
375 if (event.type() == ui::ET_GESTURE_BEGIN)
376 return GESTURE_BEZEL_DOWN;
377 return GESTURE_UNKNOWN;
380 std::string name = window ? window->name() : std::string();
382 const char kDesktopBackgroundView[] = "DesktopBackgroundView";
383 if (name == kDesktopBackgroundView) {
384 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
385 return GESTURE_DESKTOP_SCROLL;
386 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
387 return GESTURE_DESKTOP_PINCH;
388 return GESTURE_UNKNOWN;
391 const char kWebPage[] = "RenderWidgetHostViewAura";
392 if (name == kWebPage) {
393 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
394 return GESTURE_WEBPAGE_PINCH;
395 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
396 return GESTURE_WEBPAGE_SCROLL;
397 if (event.type() == ui::ET_GESTURE_TAP)
398 return GESTURE_WEBPAGE_TAP;
399 return GESTURE_UNKNOWN;
402 views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
403 if (!widget)
404 return GESTURE_UNKNOWN;
406 views::View* view = widget->GetRootView()->
407 GetEventHandlerForPoint(event.location());
408 if (!view)
409 return GESTURE_UNKNOWN;
411 name = view->GetClassName();
413 const char kTabStrip[] = "TabStrip";
414 const char kTab[] = "BrowserTab";
415 if (name == kTabStrip || name == kTab) {
416 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
417 return GESTURE_TABSTRIP_SCROLL;
418 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
419 return GESTURE_TABSTRIP_PINCH;
420 if (event.type() == ui::ET_GESTURE_TAP)
421 return GESTURE_TABSTRIP_TAP;
422 return GESTURE_UNKNOWN;
425 const char kOmnibox[] = "BrowserOmniboxViewViews";
426 if (name == kOmnibox) {
427 if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
428 return GESTURE_OMNIBOX_SCROLL;
429 if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
430 return GESTURE_OMNIBOX_PINCH;
431 return GESTURE_UNKNOWN;
434 return GESTURE_UNKNOWN;
437 } // namespace ash