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/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/base/events/event.h"
     15 #include "ui/base/events/event_utils.h"
     16 #include "ui/gfx/point_conversions.h"
     17 
     18 #if defined(USE_XI2_MT)
     19 #include <X11/extensions/XInput2.h>
     20 #include <X11/Xlib.h>
     21 #endif
     22 
     23 namespace {
     24 
     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
     61 };
     62 
     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_;
     67 
     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_;
     71 
     72   // Last time-stamp of the last touch-end event.
     73   base::TimeDelta last_release_time_;
     74 
     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_;
     79 };
     80 
     81 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
     82                                  kWindowTouchDetails,
     83                                  NULL);
     84 
     85 
     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;
    112     }
    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;
    125     }
    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;
    146     }
    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;
    159     }
    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;
    168   }
    169 }
    170 
    171 }
    172 
    173 namespace ash {
    174 
    175 // static
    176 TouchUMA* TouchUMA::GetInstance() {
    177   return Singleton<TouchUMA>::get();
    178 }
    179 
    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);
    185 
    186   GestureActionType action = FindGestureActionType(target, event);
    187   RecordGestureAction(action);
    188 
    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;
    196     }
    197     details->last_mt_time_ = event.time_stamp();
    198   }
    199 }
    200 
    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);
    206 }
    207 
    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);
    213 
    214   UpdateBurstData(event);
    215 
    216   WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
    217   if (!details) {
    218     details = new WindowTouchDetails;
    219     target->SetProperty(kWindowTouchDetails, details);
    220   }
    221 
    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);
    229 
    230   gfx::Point position = event.root_location();
    231 
    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());
    247     }
    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()));
    253   }
    254 
    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())));
    257 
    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);
    264 
    265   if (event.type() == ui::ET_TOUCH_PRESSED) {
    266     Shell::GetInstance()->delegate()->RecordUserMetricsAction(
    267         UMA_TOUCHSCREEN_TAP_DOWN);
    268 
    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();
    272 
    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());
    283       }
    284     }
    285 
    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());
    296 
    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);
    309         }
    310       }
    311     }
    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());
    322     }
    323 
    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);
    330     }
    331 
    332     UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
    333 
    334     details->last_move_time_[event.touch_id()] = event.time_stamp();
    335     details->last_touch_position_[event.touch_id()] = event.location();
    336   }
    337 }
    338 
    339 TouchUMA::TouchUMA()
    340     : touch_in_progress_(false),
    341       burst_length_(0) {
    342 }
    343 
    344 TouchUMA::~TouchUMA() {
    345 }
    346 
    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));
    355         }
    356         burst_length_ = 1;
    357       } else {
    358         ++burst_length_;
    359       }
    360     }
    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;
    366   }
    367 }
    368 
    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;
    378   }
    379 
    380   std::string name = window ? window->name() : std::string();
    381 
    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;
    389   }
    390 
    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;
    400   }
    401 
    402   views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
    403   if (!widget)
    404     return GESTURE_UNKNOWN;
    405 
    406   views::View* view = widget->GetRootView()->
    407       GetEventHandlerForPoint(event.location());
    408   if (!view)
    409     return GESTURE_UNKNOWN;
    410 
    411   name = view->GetClassName();
    412 
    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;
    423   }
    424 
    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;
    432   }
    433 
    434   return GESTURE_UNKNOWN;
    435 }
    436 
    437 }  // namespace ash
    438