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
24namespace {
25
26struct 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
44DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
45                                 kWindowTouchDetails,
46                                 NULL);
47}
48
49namespace ash {
50
51// static
52TouchUMA* TouchUMA::GetInstance() {
53  return Singleton<TouchUMA>::get();
54}
55
56void 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
73void 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
80void 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
210TouchUMA::TouchUMA()
211    : is_single_finger_gesture_(false),
212      touch_in_progress_(false),
213      burst_length_(0) {
214}
215
216TouchUMA::~TouchUMA() {
217}
218
219void 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
244TouchUMA::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
313