xref: /aosp_15_r20/external/libchrome-gestures/src/metrics_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
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