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