diff options
Diffstat (limited to 'chromium/ash/touch/touch_uma.cc')
-rw-r--r-- | chromium/ash/touch/touch_uma.cc | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/chromium/ash/touch/touch_uma.cc b/chromium/ash/touch/touch_uma.cc new file mode 100644 index 00000000000..6f970ac7804 --- /dev/null +++ b/chromium/ash/touch/touch_uma.cc @@ -0,0 +1,445 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/touch/touch_uma.h" + +#include "ash/metrics/user_metrics_recorder.h" +#include "ash/shell.h" +#include "base/metrics/histogram.h" +#include "base/strings/stringprintf.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" +#include "ui/events/event.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/point_conversions.h" + +#if defined(USE_XI2_MT) +#include <X11/extensions/XInput2.h> +#include <X11/Xlib.h> +#endif + +namespace { + +enum UMAEventType { + UMA_ET_UNKNOWN, + UMA_ET_TOUCH_RELEASED, + UMA_ET_TOUCH_PRESSED, + UMA_ET_TOUCH_MOVED, + UMA_ET_TOUCH_STATIONARY, + UMA_ET_TOUCH_CANCELLED, + UMA_ET_GESTURE_SCROLL_BEGIN, + UMA_ET_GESTURE_SCROLL_END, + UMA_ET_GESTURE_SCROLL_UPDATE, + UMA_ET_GESTURE_TAP, + UMA_ET_GESTURE_TAP_DOWN, + UMA_ET_GESTURE_BEGIN, + UMA_ET_GESTURE_END, + UMA_ET_GESTURE_DOUBLE_TAP, + UMA_ET_GESTURE_TRIPLE_TAP, + UMA_ET_GESTURE_TWO_FINGER_TAP, + UMA_ET_GESTURE_PINCH_BEGIN, + UMA_ET_GESTURE_PINCH_END, + UMA_ET_GESTURE_PINCH_UPDATE, + UMA_ET_GESTURE_LONG_PRESS, + UMA_ET_GESTURE_MULTIFINGER_SWIPE, + UMA_ET_SCROLL, + UMA_ET_SCROLL_FLING_START, + UMA_ET_SCROLL_FLING_CANCEL, + UMA_ET_GESTURE_MULTIFINGER_SWIPE_3, + UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P, // 4+ fingers + UMA_ET_GESTURE_SCROLL_UPDATE_2, + UMA_ET_GESTURE_SCROLL_UPDATE_3, + UMA_ET_GESTURE_SCROLL_UPDATE_4P, + UMA_ET_GESTURE_PINCH_UPDATE_3, + UMA_ET_GESTURE_PINCH_UPDATE_4P, + UMA_ET_GESTURE_LONG_TAP, + UMA_ET_GESTURE_SHOW_PRESS, + UMA_ET_GESTURE_TAP_CANCEL, + // NOTE: Add new event types only immediately above this line. Make sure to + // update the enum list in tools/metrics/histogram/histograms.xml accordingly. + UMA_ET_COUNT +}; + +struct WindowTouchDetails { + // Move and start times of the touch points. The key is the touch-id. + std::map<int, base::TimeDelta> last_move_time_; + std::map<int, base::TimeDelta> last_start_time_; + + // The first and last positions of the touch points. + std::map<int, gfx::Point> start_touch_position_; + std::map<int, gfx::Point> last_touch_position_; + + // Last time-stamp of the last touch-end event. + base::TimeDelta last_release_time_; + + // Stores the time of the last touch released on this window (if there was a + // multi-touch gesture on the window, then this is the release-time of the + // last touch on the window). + base::TimeDelta last_mt_time_; +}; + +DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails, + kWindowTouchDetails, + NULL); + + +UMAEventType UMAEventTypeFromEvent(const ui::Event& event) { + switch (event.type()) { + case ui::ET_TOUCH_RELEASED: + return UMA_ET_TOUCH_RELEASED; + case ui::ET_TOUCH_PRESSED: + return UMA_ET_TOUCH_PRESSED; + case ui::ET_TOUCH_MOVED: + return UMA_ET_TOUCH_MOVED; + case ui::ET_TOUCH_STATIONARY: + return UMA_ET_TOUCH_STATIONARY; + case ui::ET_TOUCH_CANCELLED: + return UMA_ET_TOUCH_CANCELLED; + case ui::ET_GESTURE_SCROLL_BEGIN: + return UMA_ET_GESTURE_SCROLL_BEGIN; + case ui::ET_GESTURE_SCROLL_END: + return UMA_ET_GESTURE_SCROLL_END; + case ui::ET_GESTURE_SCROLL_UPDATE: { + const ui::GestureEvent& gesture = + static_cast<const ui::GestureEvent&>(event); + if (gesture.details().touch_points() >= 4) + return UMA_ET_GESTURE_SCROLL_UPDATE_4P; + else if (gesture.details().touch_points() == 3) + return UMA_ET_GESTURE_SCROLL_UPDATE_3; + else if (gesture.details().touch_points() == 2) + return UMA_ET_GESTURE_SCROLL_UPDATE_2; + return UMA_ET_GESTURE_SCROLL_UPDATE; + } + case ui::ET_GESTURE_TAP: { + const ui::GestureEvent& gesture = + static_cast<const ui::GestureEvent&>(event); + int tap_count = gesture.details().tap_count(); + if (tap_count == 1) + return UMA_ET_GESTURE_TAP; + if (tap_count == 2) + return UMA_ET_GESTURE_DOUBLE_TAP; + if (tap_count == 3) + return UMA_ET_GESTURE_TRIPLE_TAP; + NOTREACHED() << "Received tap with tapcount " << tap_count; + return UMA_ET_UNKNOWN; + } + case ui::ET_GESTURE_TAP_DOWN: + return UMA_ET_GESTURE_TAP_DOWN; + case ui::ET_GESTURE_BEGIN: + return UMA_ET_GESTURE_BEGIN; + case ui::ET_GESTURE_END: + return UMA_ET_GESTURE_END; + case ui::ET_GESTURE_TWO_FINGER_TAP: + return UMA_ET_GESTURE_TWO_FINGER_TAP; + case ui::ET_GESTURE_PINCH_BEGIN: + return UMA_ET_GESTURE_PINCH_BEGIN; + case ui::ET_GESTURE_PINCH_END: + return UMA_ET_GESTURE_PINCH_END; + case ui::ET_GESTURE_PINCH_UPDATE: { + const ui::GestureEvent& gesture = + static_cast<const ui::GestureEvent&>(event); + if (gesture.details().touch_points() >= 4) + return UMA_ET_GESTURE_PINCH_UPDATE_4P; + else if (gesture.details().touch_points() == 3) + return UMA_ET_GESTURE_PINCH_UPDATE_3; + return UMA_ET_GESTURE_PINCH_UPDATE; + } + case ui::ET_GESTURE_LONG_PRESS: + return UMA_ET_GESTURE_LONG_PRESS; + case ui::ET_GESTURE_LONG_TAP: + return UMA_ET_GESTURE_LONG_TAP; + case ui::ET_GESTURE_MULTIFINGER_SWIPE: { + const ui::GestureEvent& gesture = + static_cast<const ui::GestureEvent&>(event); + if (gesture.details().touch_points() >= 4) + return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P; + else if (gesture.details().touch_points() == 3) + return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3; + return UMA_ET_GESTURE_MULTIFINGER_SWIPE; + } + case ui::ET_GESTURE_TAP_CANCEL: + return UMA_ET_GESTURE_TAP_CANCEL; + case ui::ET_GESTURE_SHOW_PRESS: + return UMA_ET_GESTURE_SHOW_PRESS; + case ui::ET_SCROLL: + return UMA_ET_SCROLL; + case ui::ET_SCROLL_FLING_START: + return UMA_ET_SCROLL_FLING_START; + case ui::ET_SCROLL_FLING_CANCEL: + return UMA_ET_SCROLL_FLING_CANCEL; + default: + NOTREACHED(); + return UMA_ET_UNKNOWN; + } +} + +} + +namespace ash { + +// static +TouchUMA* TouchUMA::GetInstance() { + return Singleton<TouchUMA>::get(); +} + +void TouchUMA::RecordGestureEvent(aura::Window* target, + const ui::GestureEvent& event) { + UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated", + UMAEventTypeFromEvent(event), + UMA_ET_COUNT); + + GestureActionType action = FindGestureActionType(target, event); + RecordGestureAction(action); + + if (event.type() == ui::ET_GESTURE_END && + event.details().touch_points() == 2) { + WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); + if (!details) { + LOG(ERROR) << "Window received gesture events without receiving any touch" + " events"; + return; + } + details->last_mt_time_ = event.time_stamp(); + } +} + +void TouchUMA::RecordGestureAction(GestureActionType action) { + if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT) + return; + UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action, + GESTURE_ACTION_COUNT); +} + +void TouchUMA::RecordTouchEvent(aura::Window* target, + const ui::TouchEvent& event) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius", + static_cast<int>(std::max(event.radius_x(), event.radius_y())), + 1, 500, 100); + + UpdateBurstData(event); + + WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails); + if (!details) { + details = new WindowTouchDetails; + target->SetProperty(kWindowTouchDetails, details); + } + + // Record the location of the touch points. + const int kBucketCountForLocation = 100; + const gfx::Rect bounds = target->GetRootWindow()->bounds(); + const int bucket_size_x = std::max(1, + bounds.width() / kBucketCountForLocation); + const int bucket_size_y = std::max(1, + bounds.height() / kBucketCountForLocation); + + gfx::Point position = event.root_location(); + + // Prefer raw event location (when available) over calibrated location. + if (event.HasNativeEvent()) { +#if defined(USE_XI2_MT) + XEvent* xevent = event.native_event(); + CHECK_EQ(GenericEvent, xevent->type); + XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data); + if (xievent->evtype == XI_TouchBegin || + xievent->evtype == XI_TouchUpdate || + xievent->evtype == XI_TouchEnd) { + XIDeviceEvent* device_event = + static_cast<XIDeviceEvent*>(xevent->xcookie.data); + position.SetPoint(static_cast<int>(device_event->event_x), + static_cast<int>(device_event->event_y)); + } else { + position = ui::EventLocationFromNative(event.native_event()); + } +#else + position = ui::EventLocationFromNative(event.native_event()); +#endif + position = gfx::ToFlooredPoint( + gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor())); + } + + position.set_x(std::min(bounds.width() - 1, std::max(0, position.x()))); + position.set_y(std::min(bounds.height() - 1, std::max(0, position.y()))); + + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX", + position.x() / bucket_size_x, + 0, kBucketCountForLocation, kBucketCountForLocation + 1); + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY", + position.y() / bucket_size_y, + 0, kBucketCountForLocation, kBucketCountForLocation + 1); + + if (event.type() == ui::ET_TOUCH_PRESSED) { + Shell::GetInstance()->metrics()->RecordUserMetricsAction( + UMA_TOUCHSCREEN_TAP_DOWN); + + details->last_start_time_[event.touch_id()] = event.time_stamp(); + details->start_touch_position_[event.touch_id()] = event.root_location(); + details->last_touch_position_[event.touch_id()] = event.location(); + + if (details->last_release_time_.ToInternalValue()) { + // Measuring the interval between a touch-release and the next + // touch-start is probably less useful when doing multi-touch (e.g. + // gestures, or multi-touch friendly apps). So count this only if the user + // hasn't done any multi-touch during the last 30 seconds. + base::TimeDelta diff = event.time_stamp() - details->last_mt_time_; + if (diff.InSeconds() > 30) { + base::TimeDelta gap = event.time_stamp() - details->last_release_time_; + UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd", + gap.InMilliseconds()); + } + } + + // Record the number of touch-points currently active for the window. + const int kMaxTouchPoints = 10; + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints", + details->last_start_time_.size(), + 1, kMaxTouchPoints, kMaxTouchPoints + 1); + } else if (event.type() == ui::ET_TOUCH_RELEASED) { + if (details->last_start_time_.count(event.touch_id())) { + base::TimeDelta duration = event.time_stamp() - + details->last_start_time_[event.touch_id()]; + UMA_HISTOGRAM_TIMES("Ash.TouchDuration2", duration); + + // Look for touches that were [almost] stationary for a long time. + const double kLongStationaryTouchDuration = 10; + const int kLongStationaryTouchDistanceSquared = 100; + if (duration.InSecondsF() > kLongStationaryTouchDuration) { + gfx::Vector2d distance = event.root_location() - + details->start_touch_position_[event.touch_id()]; + if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration", + duration.InSeconds(), + kLongStationaryTouchDuration, + 1000, + 20); + } + } + } + details->last_start_time_.erase(event.touch_id()); + details->last_move_time_.erase(event.touch_id()); + details->start_touch_position_.erase(event.touch_id()); + details->last_touch_position_.erase(event.touch_id()); + details->last_release_time_ = event.time_stamp(); + } else if (event.type() == ui::ET_TOUCH_MOVED) { + int distance = 0; + if (details->last_touch_position_.count(event.touch_id())) { + gfx::Point lastpos = details->last_touch_position_[event.touch_id()]; + distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y()); + } + + if (details->last_move_time_.count(event.touch_id())) { + base::TimeDelta move_delay = event.time_stamp() - + details->last_move_time_[event.touch_id()]; + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval", + move_delay.InMilliseconds(), + 1, 50, 25); + } + + UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50); + + details->last_move_time_[event.touch_id()] = event.time_stamp(); + details->last_touch_position_[event.touch_id()] = event.location(); + } +} + +TouchUMA::TouchUMA() + : touch_in_progress_(false), + burst_length_(0) { +} + +TouchUMA::~TouchUMA() { +} + +void TouchUMA::UpdateBurstData(const ui::TouchEvent& event) { + if (event.type() == ui::ET_TOUCH_PRESSED) { + if (!touch_in_progress_) { + base::TimeDelta difference = event.time_stamp() - last_touch_down_time_; + if (difference > base::TimeDelta::FromMilliseconds(250)) { + if (burst_length_) { + UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst", + std::min(burst_length_, 100)); + } + burst_length_ = 1; + } else { + ++burst_length_; + } + } + touch_in_progress_ = true; + last_touch_down_time_ = event.time_stamp(); + } else if (event.type() == ui::ET_TOUCH_RELEASED) { + if (!aura::Env::GetInstance()->is_touch_down()) + touch_in_progress_ = false; + } +} + +TouchUMA::GestureActionType TouchUMA::FindGestureActionType( + aura::Window* window, + const ui::GestureEvent& event) { + if (!window || window->GetRootWindow() == window) { + if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) + return GESTURE_BEZEL_SCROLL; + if (event.type() == ui::ET_GESTURE_BEGIN) + return GESTURE_BEZEL_DOWN; + return GESTURE_UNKNOWN; + } + + std::string name = window ? window->name() : std::string(); + + const char kDesktopBackgroundView[] = "DesktopBackgroundView"; + if (name == kDesktopBackgroundView) { + if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) + return GESTURE_DESKTOP_SCROLL; + if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) + return GESTURE_DESKTOP_PINCH; + return GESTURE_UNKNOWN; + } + + const char kWebPage[] = "RenderWidgetHostViewAura"; + if (name == kWebPage) { + if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) + return GESTURE_WEBPAGE_PINCH; + if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) + return GESTURE_WEBPAGE_SCROLL; + if (event.type() == ui::ET_GESTURE_TAP) + return GESTURE_WEBPAGE_TAP; + return GESTURE_UNKNOWN; + } + + views::Widget* widget = views::Widget::GetWidgetForNativeView(window); + if (!widget) + return GESTURE_UNKNOWN; + + views::View* view = widget->GetRootView()-> + GetEventHandlerForPoint(event.location()); + if (!view) + return GESTURE_UNKNOWN; + + name = view->GetClassName(); + + const char kTabStrip[] = "TabStrip"; + const char kTab[] = "BrowserTab"; + if (name == kTabStrip || name == kTab) { + if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) + return GESTURE_TABSTRIP_SCROLL; + if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) + return GESTURE_TABSTRIP_PINCH; + if (event.type() == ui::ET_GESTURE_TAP) + return GESTURE_TABSTRIP_TAP; + return GESTURE_UNKNOWN; + } + + const char kOmnibox[] = "BrowserOmniboxViewViews"; + if (name == kOmnibox) { + if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) + return GESTURE_OMNIBOX_SCROLL; + if (event.type() == ui::ET_GESTURE_PINCH_BEGIN) + return GESTURE_OMNIBOX_PINCH; + return GESTURE_UNKNOWN; + } + + return GESTURE_UNKNOWN; +} + +} // namespace ash |