• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The ChromiumOS Authors
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 "include/metrics_filter_interpreter.h"
6 
7 #include <cmath>
8 
9 #include "include/filter_interpreter.h"
10 #include "include/finger_metrics.h"
11 #include "include/gestures.h"
12 #include "include/logging.h"
13 #include "include/prop_registry.h"
14 #include "include/tracer.h"
15 #include "include/util.h"
16 
17 namespace gestures {
18 
MetricsFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer,GestureInterpreterDeviceClass devclass)19 MetricsFilterInterpreter::MetricsFilterInterpreter(
20     PropRegistry* prop_reg,
21     Interpreter* next,
22     Tracer* tracer,
23     GestureInterpreterDeviceClass devclass)
24     : FilterInterpreter(NULL, next, tracer, false),
25       devclass_(devclass),
26       mouse_movement_session_index_(0),
27       mouse_movement_current_session_length(0),
28       mouse_movement_current_session_start(0),
29       mouse_movement_current_session_last(0),
30       mouse_movement_current_session_distance(0),
31       noisy_ground_distance_threshold_(prop_reg,
32                                        "Metrics Noisy Ground Distance",
33                                        10.0),
34       noisy_ground_time_threshold_(prop_reg, "Metrics Noisy Ground Time", 0.1),
35       mouse_moving_time_threshold_(prop_reg,
36                                    "Metrics Mouse Moving Time",
37                                    0.05),
38       mouse_control_warmup_sessions_(prop_reg,
39                                      "Metrics Mouse Warmup Session",
40                                      100) {
41   InitName();
42 }
43 
SyncInterpretImpl(HardwareState * hwstate,stime_t * timeout)44 void MetricsFilterInterpreter::SyncInterpretImpl(HardwareState* hwstate,
45                                                  stime_t* timeout) {
46   if (devclass_ == GESTURES_DEVCLASS_TOUCHPAD) {
47     // Right now, we only want to update finger states for built-in touchpads
48     // because all the generated metrics gestures would be put under each
49     // platform's hat on the Chrome UMA dashboard. If we send metrics gestures
50     // (e.g. noisy ground instances) for external peripherals (e.g. multi-touch
51     // mice), they would be mistaken as from the platform's touchpad and thus
52     // results in over-counting.
53     //
54     // TODO(sheckylin): Don't send metric gestures for external touchpads
55     // either.
56     // TODO(sheckylin): Track finger related metrics for external peripherals
57     // as well after gaining access to the UMA log.
58     UpdateFingerState(*hwstate);
59   } else if (devclass_ == GESTURES_DEVCLASS_MOUSE ||
60              devclass_ == GESTURES_DEVCLASS_MULTITOUCH_MOUSE ||
61              devclass_ == GESTURES_DEVCLASS_POINTING_STICK) {
62     UpdateMouseMovementState(*hwstate);
63   }
64   next_->SyncInterpret(hwstate, timeout);
65 }
66 
AddNewStateToBuffer(FingerHistory & history,const FingerState & data,const HardwareState & hwstate)67 void MetricsFilterInterpreter::AddNewStateToBuffer(
68     FingerHistory& history,
69     const FingerState& data,
70     const HardwareState& hwstate) {
71   // The history buffer is already full, pop one
72   if (history.size() == MState::MaxHistorySize())
73     history.pop_front();
74 
75   // Push the new finger state to the back of buffer
76   (void)history.emplace_back(data, hwstate);
77 }
78 
UpdateMouseMovementState(const HardwareState & hwstate)79 void MetricsFilterInterpreter::UpdateMouseMovementState(
80     const HardwareState& hwstate) {
81   // Skip finger-only hardware states for multi-touch mice.
82   if (hwstate.rel_x == 0 && hwstate.rel_y == 0)
83     return;
84 
85   // If the last movement is too long ago, we consider the history
86   // an independent session. Report statistic for it and start a new
87   // one.
88   if (mouse_movement_current_session_length >= 1 &&
89       (hwstate.timestamp - mouse_movement_current_session_last >
90        mouse_moving_time_threshold_.val_)) {
91     // We skip the first a few sessions right after the user starts using the
92     // mouse because they tend to be more noisy.
93     if (mouse_movement_session_index_ >= mouse_control_warmup_sessions_.val_)
94       ReportMouseStatistics();
95     mouse_movement_current_session_length = 0;
96     mouse_movement_current_session_distance = 0;
97     ++mouse_movement_session_index_;
98   }
99 
100   // We skip the movement of the first event because there is no way to tell
101   // the start time of it.
102   if (!mouse_movement_current_session_length) {
103     mouse_movement_current_session_start = hwstate.timestamp;
104   } else {
105     mouse_movement_current_session_distance +=
106         sqrtf(hwstate.rel_x * hwstate.rel_x + hwstate.rel_y * hwstate.rel_y);
107   }
108   mouse_movement_current_session_last = hwstate.timestamp;
109   ++mouse_movement_current_session_length;
110 }
111 
ReportMouseStatistics()112 void MetricsFilterInterpreter::ReportMouseStatistics() {
113   // At least 2 samples are needed to compute delta t.
114   if (mouse_movement_current_session_length == 1)
115     return;
116 
117   // Compute the average speed.
118   stime_t session_time = mouse_movement_current_session_last -
119                          mouse_movement_current_session_start;
120   double avg_speed = mouse_movement_current_session_distance / session_time;
121 
122   // Send the metrics gesture.
123   ProduceGesture(Gesture(kGestureMetrics,
124                          mouse_movement_current_session_start,
125                          mouse_movement_current_session_last,
126                          kGestureMetricsTypeMouseMovement,
127                          avg_speed,
128                          session_time));
129 }
130 
UpdateFingerState(const HardwareState & hwstate)131 void MetricsFilterInterpreter::UpdateFingerState(
132     const HardwareState& hwstate) {
133   RemoveMissingIdsFromMap(&histories_, hwstate);
134 
135   FingerState *fs = hwstate.fingers;
136   for (short i = 0; i < hwstate.finger_cnt; i++) {
137     // Update the map if the contact is new
138     if (!MapContainsKey(histories_, fs[i].tracking_id)) {
139       histories_[fs[i].tracking_id] = FingerHistory{};
140     }
141     auto& href = histories_[fs[i].tracking_id];
142 
143     // Check if the finger history contains interesting patterns
144     AddNewStateToBuffer(href, fs[i], hwstate);
145     DetectNoisyGround(href);
146   }
147 }
148 
DetectNoisyGround(FingerHistory & history)149 bool MetricsFilterInterpreter::DetectNoisyGround(FingerHistory& history) {
150   // Noise pattern takes 3 samples
151   if (history.size() < 3)
152     return false;
153 
154   auto current = history.at(-1);
155   auto past_1 = history.at(-2);
156   auto past_2 = history.at(-3);
157   // Noise pattern needs to happen in a short period of time
158   if (current.timestamp - past_2.timestamp > noisy_ground_time_threshold_.val_)
159     return false;
160 
161   // vec[when][x,y]
162   float vec[2][2];
163   vec[0][0] = current.data.position_x - past_1.data.position_x;
164   vec[0][1] = current.data.position_y - past_1.data.position_y;
165   vec[1][0] = past_1.data.position_x - past_2.data.position_x;
166   vec[1][1] = past_1.data.position_y - past_2.data.position_y;
167   const float thr = noisy_ground_distance_threshold_.val_;
168   // We dictate the noise pattern as two consecutive big moves in
169   // opposite directions in either X or Y
170   for (size_t i = 0; i < arraysize(vec[0]); i++)
171     if ((vec[0][i] < -thr && vec[1][i] > thr) ||
172         (vec[0][i] > thr && vec[1][i] < -thr)) {
173       ProduceGesture(Gesture(kGestureMetrics, past_2.timestamp,
174                      current.timestamp, kGestureMetricsTypeNoisyGround,
175                      vec[0][i], vec[1][i]));
176       return true;
177     }
178   return false;
179 }
180 
181 }  // namespace gestures
182