• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(NULL, 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, NULL, mprops, consumer);
56 }
57 
SyncInterpretImpl(HardwareState * hwstate,stime_t * timeout)58 void HapticButtonGeneratorFilterInterpreter::SyncInterpretImpl(
59     HardwareState* hwstate, stime_t* timeout) {
60   HandleHardwareState(hwstate);
61   stime_t next_timeout = NO_DEADLINE;
62   next_->SyncInterpret(hwstate, &next_timeout);
63   UpdatePalmState(hwstate);
64   *timeout = SetNextDeadlineAndReturnTimeoutVal(
65       hwstate->timestamp, active_gesture_deadline_, next_timeout);
66 }
67 
HandleHardwareState(HardwareState * hwstate)68 void HapticButtonGeneratorFilterInterpreter::HandleHardwareState(
69     HardwareState* hwstate) {
70   if (!enabled_.val_ || !is_haptic_pad_)
71     return;
72 
73   // Ignore the button state generated by the haptic touchpad.
74   hwstate->buttons_down = 0;
75 
76   // Determine force thresholds.
77   double down_threshold;
78   double up_threshold;
79   if (use_custom_thresholds_.val_) {
80     down_threshold = custom_down_threshold_.val_;
81     up_threshold = custom_up_threshold_.val_;
82   }
83   else {
84     down_threshold = down_thresholds_[sensitivity_.val_ - 1];
85     up_threshold = up_thresholds_[sensitivity_.val_ - 1];
86   }
87 
88   if (use_dynamic_thresholds_.val_) {
89     up_threshold = fmax(up_threshold, dynamic_up_threshold_);
90     down_threshold = fmax(down_threshold, dynamic_down_threshold_);
91   }
92 
93   up_threshold *= release_suppress_factor_;
94 
95   // Determine maximum force on touchpad in grams
96   double force = 0.0;
97   for (short i = 0; i < hwstate->finger_cnt; i++) {
98     FingerState* fs = &hwstate->fingers[i];
99     if (!SetContainsValue(palms_, fs->tracking_id)) {
100       force = fmax(force, fs->pressure);
101     }
102   }
103   force *= force_scale_.val_;
104   force += force_translate_.val_;
105 
106   // Set the button state
107   bool prev_button_down = button_down_;
108   if (button_down_) {
109     if (force < up_threshold)
110       button_down_ = false;
111     else
112       hwstate->buttons_down = GESTURES_BUTTON_LEFT;
113   } else if (force > down_threshold && !active_gesture_) {
114     button_down_ = true;
115     hwstate->buttons_down = GESTURES_BUTTON_LEFT;
116   }
117 
118   // When the user presses very hard, we want to increase the force threshold
119   // for releasing the button. We scale the release threshold as a ratio of the
120   // max force applied while the button is down.
121   if (prev_button_down) {
122     if (button_down_) {
123       dynamic_up_threshold_ = fmax(dynamic_up_threshold_,
124                                    force * dynamic_up_ratio_.val_);
125       dynamic_up_threshold_ = fmin(dynamic_up_threshold_,
126                                    max_dynamic_up_force_.val_);
127     } else {
128       dynamic_up_threshold_ = 0.0;
129     }
130   }
131 
132   // Because we dynamically increase the up_threshold when a user presses very
133   // hard, we also need to increase the down_threshold for the next click.
134   // However, if the user continues to decrease force after the button release,
135   // event, we will keep scaling down the dynamic_down_threshold.
136   if (prev_button_down) {
137     if (!button_down_) {
138       dynamic_down_threshold_ = force * dynamic_down_ratio_.val_;
139     }
140   } else {
141     if (button_down_) {
142       dynamic_down_threshold_ = 0.0;
143     } else {
144       dynamic_down_threshold_ = fmin(dynamic_down_threshold_,
145                                      force * dynamic_down_ratio_.val_);
146     }
147   }
148   release_suppress_factor_ = 1.0;
149 }
150 
UpdatePalmState(HardwareState * hwstate)151 void HapticButtonGeneratorFilterInterpreter::UpdatePalmState(
152     HardwareState* hwstate) {
153   RemoveMissingIdsFromSet(&palms_, *hwstate);
154   for (short i = 0; i < hwstate->finger_cnt; i++) {
155     FingerState* fs = &hwstate->fingers[i];
156     if (fs->flags & GESTURES_FINGER_LARGE_PALM) {
157       palms_.insert(fs->tracking_id);
158     }
159   }
160 }
161 
162 
HandleTimerImpl(stime_t now,stime_t * timeout)163 void HapticButtonGeneratorFilterInterpreter::HandleTimerImpl(
164     stime_t now, stime_t *timeout) {
165   stime_t next_timeout;
166   if (ShouldCallNextTimer(active_gesture_deadline_)) {
167     next_timeout = NO_DEADLINE;
168     next_->HandleTimer(now, &next_timeout);
169   } else {
170     if (active_gesture_deadline_ > now) {
171       Err("Spurious callback. now: %f, active gesture deadline: %f",
172           now, active_gesture_deadline_);
173       return;
174     }
175     // If enough time has passed without an active gesture event assume that we
176     // missed the gesture ending event, to prevent a state where the button is
177     // stuck down.
178     active_gesture_ = false;
179 
180     active_gesture_deadline_ = NO_DEADLINE;
181     next_timeout =
182       next_timer_deadline_ == NO_DEADLINE || next_timer_deadline_ <= now ?
183       NO_DEADLINE : next_timer_deadline_ - now;
184   }
185   *timeout = SetNextDeadlineAndReturnTimeoutVal(now,
186                                                 active_gesture_deadline_,
187                                                 next_timeout);
188 }
189 
ConsumeGesture(const Gesture & gesture)190 void HapticButtonGeneratorFilterInterpreter::ConsumeGesture(
191     const Gesture& gesture) {
192   if (!enabled_.val_ || !is_haptic_pad_) {
193     ProduceGesture(gesture);
194     return;
195   }
196 
197   // Determine if there is an active non-click multi-finger gesture.
198   switch (gesture.type) {
199     case kGestureTypeScroll:
200     case kGestureTypeSwipe:
201     case kGestureTypeFourFingerSwipe:
202       active_gesture_ = true;
203       break;
204     case kGestureTypeFling:
205     case kGestureTypeSwipeLift:
206     case kGestureTypeFourFingerSwipeLift:
207       active_gesture_ = false;
208       break;
209     case kGestureTypePinch:
210       active_gesture_ = (gesture.details.pinch.zoom_state != GESTURES_ZOOM_END);
211       break;
212     default:
213       break;
214   }
215   if (active_gesture_) {
216     active_gesture_deadline_ = gesture.end_time + active_gesture_timeout_;
217   }
218 
219   // When dragging while clicking, users often reduce the force applied, causing
220   // accidental release. So we calculate a scaling factor to reduce the "up"
221   // threshold which starts at 1.0 (normal threshold) for stationary fingers,
222   // and goes down to 0.0 at the complete_release_suppress_speed_.
223   if (gesture.type == kGestureTypeMove) {
224     float distance_sq = gesture.details.move.dx * gesture.details.move.dx +
225         gesture.details.move.dy * gesture.details.move.dy;
226     stime_t time_delta = gesture.end_time - gesture.start_time;
227     float complete_suppress_dist =
228         complete_release_suppress_speed_.val_ * time_delta;
229     float complete_suppress_dist_sq =
230         complete_suppress_dist * complete_suppress_dist;
231 
232     release_suppress_factor_ =
233         time_delta <= 0.0 ? 1.0 : 1.0 - distance_sq / complete_suppress_dist_sq;
234 
235     // Always allow release at very low force, to prevent a stuck button when
236     // the user lifts their finger while moving quickly.
237     release_suppress_factor_ = fmax(release_suppress_factor_, 0.1);
238   }
239 
240   ProduceGesture(gesture);
241 }
242 
243 }  // namespace gestures
244