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