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