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