Home | History | Annotate | Download | only in touch
      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
    313