xref: /aosp_15_r20/external/libchrome-gestures/src/haptic_button_generator_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
1 // Copyright 2021 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/haptic_button_generator_filter_interpreter.h"
6 
7 #include <math.h>
8 
9 #include "include/gestures.h"
10 #include "include/interpreter.h"
11 #include "include/logging.h"
12 #include "include/tracer.h"
13 #include "include/util.h"
14 
15 namespace gestures {
16 
HapticButtonGeneratorFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)17 HapticButtonGeneratorFilterInterpreter::HapticButtonGeneratorFilterInterpreter(
18     PropRegistry* prop_reg, Interpreter* next, Tracer* tracer)
19     : FilterInterpreter(nullptr, next, tracer, false),
20       release_suppress_factor_(1.0),
21       active_gesture_(false),
22       active_gesture_timeout_(0.1),
23       active_gesture_deadline_(NO_DEADLINE),
24       button_down_(false),
25       dynamic_down_threshold_(0.0),
26       dynamic_up_threshold_(0.0),
27       sensitivity_(prop_reg, "Haptic Button Sensitivity", 3),
28       use_custom_thresholds_(prop_reg,
29                              "Use Custom Haptic Button Force Thresholds",
30                              false),
31       custom_down_threshold_(prop_reg,
32                              "Custom Haptic Button Force Threshold Down",
33                              150.0),
34       custom_up_threshold_(prop_reg,
35                             "Custom Haptic Button Force Threshold Up",
36                             130.0),
37       enabled_(prop_reg, "Enable Haptic Button Generation", false),
38       force_scale_(prop_reg, "Force Calibration Slope", 1.0),
39       force_translate_(prop_reg, "Force Calibration Offset", 0.0),
40       complete_release_suppress_speed_(
41           prop_reg, "Haptic Complete Release Suppression Speed", 200.0),
42       use_dynamic_thresholds_(prop_reg, "Use Dynamic Haptic Thresholds", false),
43       dynamic_down_ratio_(prop_reg, "Dynamic Haptic Down Ratio", 1.2),
44       dynamic_up_ratio_(prop_reg, "Dynamic Haptic Up Ratio", 0.5),
45       max_dynamic_up_force_(prop_reg, "Max Dynamic Haptic Up Force", 350.0) {
46   InitName();
47 }
48 
Initialize(const HardwareProperties * hwprops,Metrics * metrics,MetricsProperties * mprops,GestureConsumer * consumer)49 void HapticButtonGeneratorFilterInterpreter::Initialize(
50     const HardwareProperties* hwprops,
51     Metrics* metrics,
52     MetricsProperties* mprops,
53     GestureConsumer* consumer) {
54   is_haptic_pad_ = hwprops->is_haptic_pad;
55   FilterInterpreter::Initialize(hwprops, nullptr, mprops, consumer);
56 }
57 
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)58 void HapticButtonGeneratorFilterInterpreter::SyncInterpretImpl(
59     HardwareState& hwstate, stime_t* timeout) {
60   const char name[] =
61       "HapticButtonGeneratorFilterInterpreter::SyncInterpretImpl";
62   LogHardwareStatePre(name, hwstate);
63 
64   HandleHardwareState(hwstate);
65   stime_t next_timeout = NO_DEADLINE;
66 
67   LogHardwareStatePost(name, hwstate);
68   next_->SyncInterpret(hwstate, &next_timeout);
69   UpdatePalmState(hwstate);
70   *timeout = SetNextDeadlineAndReturnTimeoutVal(
71       hwstate.timestamp, active_gesture_deadline_, next_timeout);
72 }
73 
HandleHardwareState(HardwareState & hwstate)74 void HapticButtonGeneratorFilterInterpreter::HandleHardwareState(
75     HardwareState& hwstate) {
76   if (!enabled_.val_ || !is_haptic_pad_)
77     return;
78 
79   // Ignore the button state generated by the haptic touchpad.
80   hwstate.buttons_down = 0;
81 
82   // Determine force thresholds.
83   double down_threshold;
84   double up_threshold;
85   if (use_custom_thresholds_.val_) {
86     down_threshold = custom_down_threshold_.val_;
87     up_threshold = custom_up_threshold_.val_;
88   }
89   else {
90     down_threshold = down_thresholds_[sensitivity_.val_ - 1];
91     up_threshold = up_thresholds_[sensitivity_.val_ - 1];
92   }
93 
94   if (use_dynamic_thresholds_.val_) {
95     up_threshold = fmax(up_threshold, dynamic_up_threshold_);
96     down_threshold = fmax(down_threshold, dynamic_down_threshold_);
97   }
98 
99   up_threshold *= release_suppress_factor_;
100 
101   // Determine maximum force on touchpad in grams
102   double force = 0.0;
103   for (short i = 0; i < hwstate.finger_cnt; i++) {
104     const FingerState& fs = hwstate.fingers[i];
105     if (!SetContainsValue(palms_, fs.tracking_id)) {
106       force = fmax(force, fs.pressure);
107     }
108   }
109   force *= force_scale_.val_;
110   force += force_translate_.val_;
111 
112   // Set the button state
113   bool prev_button_down = button_down_;
114   if (button_down_) {
115     if (force < up_threshold)
116       button_down_ = false;
117     else
118       hwstate.buttons_down = GESTURES_BUTTON_LEFT;
119   } else if (force > down_threshold && !active_gesture_) {
120     button_down_ = true;
121     hwstate.buttons_down = GESTURES_BUTTON_LEFT;
122   }
123 
124   // When the user presses very hard, we want to increase the force threshold
125   // for releasing the button. We scale the release threshold as a ratio of the
126   // max force applied while the button is down.
127   if (prev_button_down) {
128     if (button_down_) {
129       dynamic_up_threshold_ = fmax(dynamic_up_threshold_,
130                                    force * dynamic_up_ratio_.val_);
131       dynamic_up_threshold_ = fmin(dynamic_up_threshold_,
132                                    max_dynamic_up_force_.val_);
133     } else {
134       dynamic_up_threshold_ = 0.0;
135     }
136   }
137 
138   // Because we dynamically increase the up_threshold when a user presses very
139   // hard, we also need to increase the down_threshold for the next click.
140   // However, if the user continues to decrease force after the button release,
141   // event, we will keep scaling down the dynamic_down_threshold.
142   if (prev_button_down) {
143     if (!button_down_) {
144       dynamic_down_threshold_ = force * dynamic_down_ratio_.val_;
145     }
146   } else {
147     if (button_down_) {
148       dynamic_down_threshold_ = 0.0;
149     } else {
150       dynamic_down_threshold_ = fmin(dynamic_down_threshold_,
151                                      force * dynamic_down_ratio_.val_);
152     }
153   }
154   release_suppress_factor_ = 1.0;
155 }
156 
UpdatePalmState(const HardwareState & hwstate)157 void HapticButtonGeneratorFilterInterpreter::UpdatePalmState(
158     const HardwareState& hwstate) {
159   RemoveMissingIdsFromSet(&palms_, hwstate);
160   for (short i = 0; i < hwstate.finger_cnt; i++) {
161     const FingerState& fs = hwstate.fingers[i];
162     if (fs.flags & GESTURES_FINGER_LARGE_PALM) {
163       palms_.insert(fs.tracking_id);
164     }
165   }
166 }
167 
168 
HandleTimerImpl(stime_t now,stime_t * timeout)169 void HapticButtonGeneratorFilterInterpreter::HandleTimerImpl(
170     stime_t now, stime_t *timeout) {
171   const char name[] = "HapticButtonGeneratorFilterInterpreter::HandleTimerImpl";
172   LogHandleTimerPre(name, now, timeout);
173 
174   stime_t next_timeout;
175   if (ShouldCallNextTimer(active_gesture_deadline_)) {
176     next_timeout = NO_DEADLINE;
177     next_->HandleTimer(now, &next_timeout);
178   } else {
179     if (active_gesture_deadline_ > now) {
180       Err("Spurious callback. now: %f, active gesture deadline: %f",
181           now, active_gesture_deadline_);
182       return;
183     }
184     // If enough time has passed without an active gesture event assume that we
185     // missed the gesture ending event, to prevent a state where the button is
186     // stuck down.
187     active_gesture_ = false;
188     active_gesture_deadline_ = NO_DEADLINE;
189     next_timeout = next_timer_deadline_ == NO_DEADLINE ||
190                    next_timer_deadline_ <= now
191                       ? NO_DEADLINE
192                       : next_timer_deadline_ - now;
193   }
194   *timeout = SetNextDeadlineAndReturnTimeoutVal(now,
195                                                 active_gesture_deadline_,
196                                                 next_timeout);
197   LogHandleTimerPost(name, now, timeout);
198 }
199 
ConsumeGesture(const Gesture & gesture)200 void HapticButtonGeneratorFilterInterpreter::ConsumeGesture(
201     const Gesture& gesture) {
202   const char name[] = "HapticButtonGeneratorFilterInterpreter::ConsumeGesture";
203   LogGestureConsume(name, gesture);
204 
205   if (!enabled_.val_ || !is_haptic_pad_) {
206     LogGestureProduce(name, gesture);
207     ProduceGesture(gesture);
208     return;
209   }
210 
211   // Determine if there is an active non-click multi-finger gesture.
212   switch (gesture.type) {
213     case kGestureTypeScroll:
214     case kGestureTypeSwipe:
215     case kGestureTypeFourFingerSwipe:
216       active_gesture_ = true;
217       break;
218     case kGestureTypeFling:
219     case kGestureTypeSwipeLift:
220     case kGestureTypeFourFingerSwipeLift:
221       active_gesture_ = false;
222       break;
223     case kGestureTypePinch:
224       active_gesture_ = (gesture.details.pinch.zoom_state != GESTURES_ZOOM_END);
225       break;
226     default:
227       break;
228   }
229   if (active_gesture_) {
230     active_gesture_deadline_ = gesture.end_time + active_gesture_timeout_;
231   }
232 
233   // When dragging while clicking, users often reduce the force applied, causing
234   // accidental release. So we calculate a scaling factor to reduce the "up"
235   // threshold which starts at 1.0 (normal threshold) for stationary fingers,
236   // and goes down to 0.0 at the complete_release_suppress_speed_.
237   if (gesture.type == kGestureTypeMove) {
238     float distance_sq = gesture.details.move.dx * gesture.details.move.dx +
239         gesture.details.move.dy * gesture.details.move.dy;
240     stime_t time_delta = gesture.end_time - gesture.start_time;
241     float complete_suppress_dist =
242         complete_release_suppress_speed_.val_ * time_delta;
243     float complete_suppress_dist_sq =
244         complete_suppress_dist * complete_suppress_dist;
245 
246     release_suppress_factor_ =
247         time_delta <= 0.0 ? 1.0 : 1.0 - distance_sq / complete_suppress_dist_sq;
248 
249     // Always allow release at very low force, to prevent a stuck button when
250     // the user lifts their finger while moving quickly.
251     release_suppress_factor_ = fmax(release_suppress_factor_, 0.1);
252   }
253 
254   LogGestureProduce(name, gesture);
255   ProduceGesture(gesture);
256 }
257 
258 }  // namespace gestures
259