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