xref: /aosp_15_r20/external/libchrome-gestures/src/palm_classifying_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
1 // Copyright 2012 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/palm_classifying_filter_interpreter.h"
6 
7 #include "include/gestures.h"
8 #include "include/interpreter.h"
9 #include "include/tracer.h"
10 #include "include/util.h"
11 
12 namespace gestures {
13 
PalmClassifyingFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)14 PalmClassifyingFilterInterpreter::PalmClassifyingFilterInterpreter(
15     PropRegistry* prop_reg, Interpreter* next,
16     Tracer* tracer)
17     : FilterInterpreter(nullptr, next, tracer, false),
18       palm_pressure_(prop_reg, "Palm Pressure", 200.0),
19       palm_width_(prop_reg, "Palm Width", 21.2),
20       multi_palm_width_(prop_reg, "Multiple Palm Width", 75.0),
21       fat_finger_pressure_ratio_(prop_reg, "Fat Finger Pressure Ratio", 1.4),
22       fat_finger_width_ratio_(prop_reg, "Fat Finger Width Ratio", 1.3),
23       fat_finger_min_dist_(prop_reg, "Fat Finger Min Move Distance", 15.0),
24       palm_edge_min_width_(prop_reg, "Tap Exclusion Border Width", 8.0),
25       palm_edge_width_(prop_reg, "Palm Edge Zone Width", 14.0),
26       palm_top_edge_min_width_(prop_reg, "Top Edge Tap Exclusion Border Width",
27                                3.0),
28       palm_edge_point_speed_(prop_reg, "Palm Edge Zone Min Point Speed", 100.0),
29       palm_eval_timeout_(prop_reg, "Palm Eval Timeout", 0.1),
30       palm_stationary_time_(prop_reg, "Palm Stationary Time", 2.0),
31       palm_stationary_distance_(prop_reg, "Palm Stationary Distance", 4.0),
32       palm_pointing_min_dist_(prop_reg,
33                               "Palm Pointing Min Move Distance",
34                               8.0),
35       palm_pointing_max_reverse_dist_(prop_reg,
36                                       "Palm Pointing Max Reverse Move Distance",
37                                       0.3),
38       palm_split_max_distance_(prop_reg, "Palm Split Maximum Distance", 4.0),
39       filter_top_edge_(prop_reg, "Palm Filter Top Edge Enable", false)
40 {
41   InitName();
42   requires_metrics_ = true;
43 }
44 
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)45 void PalmClassifyingFilterInterpreter::SyncInterpretImpl(
46     HardwareState& hwstate,
47     stime_t* timeout) {
48   const char name[] = "PalmClassifyingFilterInterpreter::SyncInterpretImpl";
49   LogHardwareStatePre(name, hwstate);
50 
51   FillOriginInfo(hwstate);
52   FillMaxPressureWidthInfo(hwstate);
53   UpdateDistanceInfo(hwstate);
54   UpdatePalmState(hwstate);
55   UpdatePalmFlags(hwstate);
56   FillPrevInfo(hwstate);
57 
58   LogHardwareStatePost(name, hwstate);
59   if (next_.get())
60     next_->SyncInterpret(hwstate, timeout);
61 }
62 
FillOriginInfo(const HardwareState & hwstate)63 void PalmClassifyingFilterInterpreter::FillOriginInfo(
64     const HardwareState& hwstate) {
65   RemoveMissingIdsFromMap(&origin_timestamps_, hwstate);
66   RemoveMissingIdsFromMap(&origin_fingerstates_, hwstate);
67   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
68     const FingerState& fs = hwstate.fingers[i];
69     if (MapContainsKey(origin_timestamps_, fs.tracking_id))
70       continue;
71     origin_timestamps_[fs.tracking_id] = hwstate.timestamp;
72     origin_fingerstates_[fs.tracking_id] = fs;
73   }
74 }
75 
FillPrevInfo(const HardwareState & hwstate)76 void PalmClassifyingFilterInterpreter::FillPrevInfo(
77     const HardwareState& hwstate) {
78   RemoveMissingIdsFromMap(&prev_fingerstates_, hwstate);
79   prev_time_ = hwstate.timestamp;
80   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
81     const FingerState& fs = hwstate.fingers[i];
82     prev_fingerstates_[fs.tracking_id] = fs;
83   }
84 }
85 
FillMaxPressureWidthInfo(const HardwareState & hwstate)86 void PalmClassifyingFilterInterpreter::FillMaxPressureWidthInfo(
87     const HardwareState& hwstate) {
88   RemoveMissingIdsFromMap(&max_pressure_, hwstate);
89   RemoveMissingIdsFromMap(&max_width_, hwstate);
90   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
91     const FingerState& fs = hwstate.fingers[i];
92     int id = fs.tracking_id;
93     if (MapContainsKey(max_pressure_, id)) {
94       if (fs.pressure > max_pressure_[id])
95         max_pressure_[id] = fs.pressure;
96       if (fs.touch_major > max_width_[id])
97         max_width_[id] = fs.touch_major;
98     } else {
99       max_pressure_[id] = fs.pressure;
100       max_width_[id] = fs.touch_major;
101     }
102   }
103 }
104 
UpdateDistanceInfo(const HardwareState & hwstate)105 void PalmClassifyingFilterInterpreter::UpdateDistanceInfo(
106     const HardwareState& hwstate) {
107   RemoveMissingIdsFromMap(&distance_positive_[0], hwstate);
108   RemoveMissingIdsFromMap(&distance_positive_[1], hwstate);
109   RemoveMissingIdsFromMap(&distance_negative_[0], hwstate);
110   RemoveMissingIdsFromMap(&distance_negative_[1], hwstate);
111   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
112     const FingerState& fs = hwstate.fingers[i];
113     int id = fs.tracking_id;
114     if (MapContainsKey(prev_fingerstates_, id)) {
115       float delta[2];
116       delta[0] = fs.position_x - prev_fingerstates_[id].position_x;
117       delta[1] = fs.position_y - prev_fingerstates_[id].position_y;
118       for (int i = 0; i < 2; i++) {
119         if (delta[i] > 0)
120           distance_positive_[i][id] += delta[i];
121         else
122           distance_negative_[i][id] -= delta[i];
123       }
124     } else {
125       distance_positive_[0][id] = 0;
126       distance_positive_[1][id] = 0;
127       distance_negative_[0][id] = 0;
128       distance_negative_[1][id] = 0;
129     }
130   }
131 }
132 
FingerNearOtherFinger(const HardwareState & hwstate,size_t finger_idx)133 bool PalmClassifyingFilterInterpreter::FingerNearOtherFinger(
134     const HardwareState& hwstate,
135     size_t finger_idx) {
136   const FingerState& fs = hwstate.fingers[finger_idx];
137   for (int i = 0; i < hwstate.finger_cnt; ++i) {
138     const FingerState& other_fs = hwstate.fingers[i];
139     if (other_fs.tracking_id == fs.tracking_id)
140       continue;
141     bool close_enough_together =
142         metrics_->CloseEnoughToGesture(Vector2(fs), Vector2(other_fs)) &&
143         !SetContainsValue(palm_, other_fs.tracking_id);
144     bool too_close_together = DistSq(fs, other_fs) <
145         palm_split_max_distance_.val_ * palm_split_max_distance_.val_;
146     if (close_enough_together && !too_close_together) {
147       was_near_other_fingers_.insert(fs.tracking_id);
148       return true;
149     }
150   }
151   return false;
152 }
153 
FingerInPalmEnvelope(const FingerState & fs)154 bool PalmClassifyingFilterInterpreter::FingerInPalmEnvelope(
155     const FingerState& fs) {
156   float limit = palm_edge_min_width_.val_ +
157       (fs.pressure / palm_pressure_.val_) *
158       (palm_edge_width_.val_ - palm_edge_min_width_.val_);
159   return fs.position_x < limit ||
160       fs.position_x > (hwprops_->right - limit) ||
161       (filter_top_edge_.val_ && fs.position_y < palm_top_edge_min_width_.val_);
162 }
163 
FingerInBottomArea(const FingerState & fs)164 bool PalmClassifyingFilterInterpreter::FingerInBottomArea(
165     const FingerState& fs) {
166   return fs.position_y > (hwprops_->bottom - palm_edge_min_width_.val_);
167 }
168 
UpdatePalmState(const HardwareState & hwstate)169 void PalmClassifyingFilterInterpreter::UpdatePalmState(
170     const HardwareState& hwstate) {
171   RemoveMissingIdsFromSet(&palm_, hwstate);
172   RemoveMissingIdsFromSet(&large_palm_, hwstate);
173   RemoveMissingIdsFromMap(&pointing_, hwstate);
174   RemoveMissingIdsFromSet(&non_stationary_palm_, hwstate);
175   RemoveMissingIdsFromSet(&fingers_not_in_edge_, hwstate);
176   RemoveMissingIdsFromSet(&was_near_other_fingers_, hwstate);
177 
178   // Some finger(s) just leaves, skip this update for stability
179   if (prev_fingerstates_.size() > hwstate.finger_cnt)
180     return;
181 
182   for (short i = 0; i < hwstate.finger_cnt; i++) {
183     const FingerState& fs = hwstate.fingers[i];
184     if (!(FingerInPalmEnvelope(fs) || FingerInBottomArea(fs)))
185       fingers_not_in_edge_.insert(fs.tracking_id);
186     // Mark anything over the palm thresh as a palm
187     if (fs.pressure >= palm_pressure_.val_ ||
188         fs.touch_major >= multi_palm_width_.val_) {
189       large_palm_.insert(fs.tracking_id);
190       palm_.insert(fs.tracking_id);
191       pointing_.erase(fs.tracking_id);
192       continue;
193     }
194     // Mark externally reported palms
195     if(fs.tool_type == FingerState::ToolType::kPalm){
196       palm_.insert(fs.tracking_id);
197       pointing_.erase(fs.tracking_id);
198     }
199   }
200 
201   if (hwstate.finger_cnt == 1 &&
202       hwstate.fingers[0].touch_major >= palm_width_.val_) {
203     large_palm_.insert(hwstate.fingers[0].tracking_id);
204     palm_.insert(hwstate.fingers[0].tracking_id);
205     pointing_.erase(hwstate.fingers[0].tracking_id);
206   }
207 
208   const float kPalmStationaryDistSq =
209       palm_stationary_distance_.val_ * palm_stationary_distance_.val_;
210   const float kFatFingerMinDistSq =
211       fat_finger_min_dist_.val_ * fat_finger_min_dist_.val_;
212   const float kFatFingerMaxPressure =
213       palm_pressure_.val_ * fat_finger_pressure_ratio_.val_;
214   const float kFatFingerMaxWidth =
215       palm_width_.val_ * fat_finger_width_ratio_.val_;
216 
217   for (short i = 0; i < hwstate.finger_cnt; i++) {
218     const FingerState& fs = hwstate.fingers[i];
219     bool prev_palm = SetContainsValue(palm_, fs.tracking_id);
220     bool prev_pointing = MapContainsKey(pointing_, fs.tracking_id);
221 
222     if (prev_palm) {
223       // If the finger's pressure & width are more like a fat finger
224       // and it has moved a lot, it might be a fat finger and remove
225       // it from palm.
226       float dist_sq = DistSq(origin_fingerstates_[fs.tracking_id], fs);
227       if (max_pressure_[fs.tracking_id] <= kFatFingerMaxPressure &&
228           max_width_[fs.tracking_id] <= kFatFingerMaxWidth &&
229           dist_sq > kFatFingerMinDistSq) {
230         large_palm_.erase(fs.tracking_id);
231         palm_.erase(fs.tracking_id);
232       } else {
233         // Lock onto palm
234         continue;
235       }
236     }
237 
238     // If the finger is recently placed, remove it from pointing/fingers.
239     // If it's still looking like pointing, it'll get readded.
240     if (FingerAge(fs.tracking_id, hwstate.timestamp) <
241         palm_eval_timeout_.val_) {
242       pointing_.erase(fs.tracking_id);
243 
244       prev_pointing = false;
245     }
246     // If another finger is close by, let this be pointing
247     bool near_finger = FingerNearOtherFinger(hwstate, i);
248     bool on_edge = FingerInPalmEnvelope(fs) ||
249         FingerInBottomArea(fs);
250     if (!prev_pointing && (near_finger || !on_edge)) {
251       unsigned reason = (near_finger ? kPointCloseToFinger : 0) |
252           ((!on_edge) ? kPointNotInEdge : 0);
253       pointing_[fs.tracking_id] = reason;
254     }
255 
256     // Check if fingers that only move within palm envelope are pointing.
257     int id = fs.tracking_id;
258     float min_dist = palm_pointing_min_dist_.val_;
259     float max_reverse_dist = palm_pointing_max_reverse_dist_.val_;
260 
261     // Ideally, we want to say that a finger is pointing if it moves only in
262     // one direction significantly without zig-zag. But due to touch sensor's
263     // inaccuratcy, we make the rule to be that a finger has to move in one
264     // direction significantly with little move in the opposite direction.
265     for (size_t j = 0; j < arraysize(distance_positive_); j++)
266       if ((distance_positive_[j][id] >= min_dist &&
267            distance_negative_[j][id] <= max_reverse_dist) ||
268           (distance_positive_[j][id] <= max_reverse_dist &&
269            distance_negative_[j][id] >= min_dist)) {
270         pointing_[id] |= kPointMoving;
271       }
272 
273     // However, if the contact has been stationary for a while since it
274     // touched down, it is a palm. We track a potential palm closely for the
275     // first amount of time to see if it fits this pattern.
276     if (FingerAge(fs.tracking_id, prev_time_) >
277         palm_stationary_time_.val_ ||
278         SetContainsValue(non_stationary_palm_, fs.tracking_id)) {
279       // Finger is too old to reconsider or is moving a lot
280       continue;
281     }
282     if (DistSq(origin_fingerstates_[fs.tracking_id], fs) >
283         kPalmStationaryDistSq || !(FingerInPalmEnvelope(fs) ||
284                                    FingerInBottomArea(fs))) {
285       // Finger moving a lot or not in palm envelope; not a stationary palm.
286       non_stationary_palm_.insert(fs.tracking_id);
287       continue;
288     }
289     if (FingerAge(fs.tracking_id, prev_time_) <=
290         palm_stationary_time_.val_ &&
291         FingerAge(fs.tracking_id, hwstate.timestamp) >
292         palm_stationary_time_.val_ &&
293         !SetContainsValue(non_stationary_palm_, fs.tracking_id) &&
294         !FingerNearOtherFinger(hwstate, i)) {
295       // Enough time has passed. Make this stationary contact a palm.
296       palm_.insert(fs.tracking_id);
297       pointing_.erase(fs.tracking_id);
298     }
299   }
300 }
301 
UpdatePalmFlags(HardwareState & hwstate)302 void PalmClassifyingFilterInterpreter::UpdatePalmFlags(HardwareState& hwstate) {
303   for (short i = 0; i < hwstate.finger_cnt; i++) {
304     FingerState* fs = &hwstate.fingers[i];
305     if (SetContainsValue(large_palm_, fs->tracking_id)) {
306       fs->flags |= GESTURES_FINGER_LARGE_PALM;
307     }
308     if (SetContainsValue(palm_, fs->tracking_id)) {
309       fs->flags |= GESTURES_FINGER_PALM;
310     } else if (!MapContainsKey(pointing_, fs->tracking_id) &&
311                !SetContainsValue(was_near_other_fingers_, fs->tracking_id)) {
312       if (FingerInPalmEnvelope(*fs)) {
313         fs->flags |= GESTURES_FINGER_PALM;
314       } else if (FingerInBottomArea(*fs)) {
315         fs->flags |= (GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y);
316       }
317     } else if (MapContainsKey(pointing_, fs->tracking_id) &&
318                FingerInPalmEnvelope(*fs)) {
319       fs->flags |= GESTURES_FINGER_POSSIBLE_PALM;
320       if (pointing_[fs->tracking_id] == kPointCloseToFinger &&
321           !FingerNearOtherFinger(hwstate, i)) {
322         // Finger was near another finger, but it's not anymore, and it was
323         // only this other finger that caused it to point. Mark it w/ warp
324         // until it moves sufficiently to have another reason to be
325         // pointing.
326         fs->flags |= (GESTURES_FINGER_WARP_X | GESTURES_FINGER_WARP_Y);
327       }
328     }
329   }
330 }
331 
FingerAge(short finger_id,stime_t now) const332 stime_t PalmClassifyingFilterInterpreter::FingerAge(short finger_id,
333                                                     stime_t now) const {
334   if (!MapContainsKey(origin_timestamps_, finger_id)) {
335     Err("Don't have record of finger age for finger %d", finger_id);
336     return -1;
337   }
338   return now - origin_timestamps_.at(finger_id);
339 }
340 
341 }  // namespace gestures
342