summaryrefslogtreecommitdiff
path: root/src/haptic_button_generator_filter_interpreter.cc
blob: 5cdad9fb2f612058d104da7314134a05212604bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "include/haptic_button_generator_filter_interpreter.h"

#include <math.h>

#include "include/gestures.h"
#include "include/interpreter.h"
#include "include/logging.h"
#include "include/tracer.h"
#include "include/util.h"

namespace gestures {

HapticButtonGeneratorFilterInterpreter::HapticButtonGeneratorFilterInterpreter(
    PropRegistry* prop_reg, Interpreter* next, Tracer* tracer)
    : FilterInterpreter(nullptr, next, tracer, false),
      release_suppress_factor_(1.0),
      active_gesture_(false),
      active_gesture_timeout_(0.1),
      active_gesture_deadline_(NO_DEADLINE),
      button_down_(false),
      dynamic_down_threshold_(0.0),
      dynamic_up_threshold_(0.0),
      sensitivity_(prop_reg, "Haptic Button Sensitivity", 3),
      use_custom_thresholds_(prop_reg,
                             "Use Custom Haptic Button Force Thresholds",
                             false),
      custom_down_threshold_(prop_reg,
                             "Custom Haptic Button Force Threshold Down",
                             150.0),
      custom_up_threshold_(prop_reg,
                            "Custom Haptic Button Force Threshold Up",
                            130.0),
      enabled_(prop_reg, "Enable Haptic Button Generation", false),
      force_scale_(prop_reg, "Force Calibration Slope", 1.0),
      force_translate_(prop_reg, "Force Calibration Offset", 0.0),
      complete_release_suppress_speed_(
          prop_reg, "Haptic Complete Release Suppression Speed", 200.0),
      use_dynamic_thresholds_(prop_reg, "Use Dynamic Haptic Thresholds", false),
      dynamic_down_ratio_(prop_reg, "Dynamic Haptic Down Ratio", 1.2),
      dynamic_up_ratio_(prop_reg, "Dynamic Haptic Up Ratio", 0.5),
      max_dynamic_up_force_(prop_reg, "Max Dynamic Haptic Up Force", 350.0) {
  InitName();
}

void HapticButtonGeneratorFilterInterpreter::Initialize(
    const HardwareProperties* hwprops,
    Metrics* metrics,
    MetricsProperties* mprops,
    GestureConsumer* consumer) {
  is_haptic_pad_ = hwprops->is_haptic_pad;
  FilterInterpreter::Initialize(hwprops, nullptr, mprops, consumer);
}

void HapticButtonGeneratorFilterInterpreter::SyncInterpretImpl(
    HardwareState& hwstate, stime_t* timeout) {
  HandleHardwareState(hwstate);
  stime_t next_timeout = NO_DEADLINE;
  next_->SyncInterpret(hwstate, &next_timeout);
  UpdatePalmState(hwstate);
  *timeout = SetNextDeadlineAndReturnTimeoutVal(
      hwstate.timestamp, active_gesture_deadline_, next_timeout);
}

void HapticButtonGeneratorFilterInterpreter::HandleHardwareState(
    HardwareState& hwstate) {
  if (!enabled_.val_ || !is_haptic_pad_)
    return;

  // Ignore the button state generated by the haptic touchpad.
  hwstate.buttons_down = 0;

  // Determine force thresholds.
  double down_threshold;
  double up_threshold;
  if (use_custom_thresholds_.val_) {
    down_threshold = custom_down_threshold_.val_;
    up_threshold = custom_up_threshold_.val_;
  }
  else {
    down_threshold = down_thresholds_[sensitivity_.val_ - 1];
    up_threshold = up_thresholds_[sensitivity_.val_ - 1];
  }

  if (use_dynamic_thresholds_.val_) {
    up_threshold = fmax(up_threshold, dynamic_up_threshold_);
    down_threshold = fmax(down_threshold, dynamic_down_threshold_);
  }

  up_threshold *= release_suppress_factor_;

  // Determine maximum force on touchpad in grams
  double force = 0.0;
  for (short i = 0; i < hwstate.finger_cnt; i++) {
    const FingerState& fs = hwstate.fingers[i];
    if (!SetContainsValue(palms_, fs.tracking_id)) {
      force = fmax(force, fs.pressure);
    }
  }
  force *= force_scale_.val_;
  force += force_translate_.val_;

  // Set the button state
  bool prev_button_down = button_down_;
  if (button_down_) {
    if (force < up_threshold)
      button_down_ = false;
    else
      hwstate.buttons_down = GESTURES_BUTTON_LEFT;
  } else if (force > down_threshold && !active_gesture_) {
    button_down_ = true;
    hwstate.buttons_down = GESTURES_BUTTON_LEFT;
  }

  // When the user presses very hard, we want to increase the force threshold
  // for releasing the button. We scale the release threshold as a ratio of the
  // max force applied while the button is down.
  if (prev_button_down) {
    if (button_down_) {
      dynamic_up_threshold_ = fmax(dynamic_up_threshold_,
                                   force * dynamic_up_ratio_.val_);
      dynamic_up_threshold_ = fmin(dynamic_up_threshold_,
                                   max_dynamic_up_force_.val_);
    } else {
      dynamic_up_threshold_ = 0.0;
    }
  }

  // Because we dynamically increase the up_threshold when a user presses very
  // hard, we also need to increase the down_threshold for the next click.
  // However, if the user continues to decrease force after the button release,
  // event, we will keep scaling down the dynamic_down_threshold.
  if (prev_button_down) {
    if (!button_down_) {
      dynamic_down_threshold_ = force * dynamic_down_ratio_.val_;
    }
  } else {
    if (button_down_) {
      dynamic_down_threshold_ = 0.0;
    } else {
      dynamic_down_threshold_ = fmin(dynamic_down_threshold_,
                                     force * dynamic_down_ratio_.val_);
    }
  }
  release_suppress_factor_ = 1.0;
}

void HapticButtonGeneratorFilterInterpreter::UpdatePalmState(
    const HardwareState& hwstate) {
  RemoveMissingIdsFromSet(&palms_, hwstate);
  for (short i = 0; i < hwstate.finger_cnt; i++) {
    const FingerState& fs = hwstate.fingers[i];
    if (fs.flags & GESTURES_FINGER_LARGE_PALM) {
      palms_.insert(fs.tracking_id);
    }
  }
}


void HapticButtonGeneratorFilterInterpreter::HandleTimerImpl(
    stime_t now, stime_t *timeout) {
  stime_t next_timeout;
  if (ShouldCallNextTimer(active_gesture_deadline_)) {
    next_timeout = NO_DEADLINE;
    next_->HandleTimer(now, &next_timeout);
  } else {
    if (active_gesture_deadline_ > now) {
      Err("Spurious callback. now: %f, active gesture deadline: %f",
          now, active_gesture_deadline_);
      return;
    }
    // If enough time has passed without an active gesture event assume that we
    // missed the gesture ending event, to prevent a state where the button is
    // stuck down.
    active_gesture_ = false;
    active_gesture_deadline_ = NO_DEADLINE;
    next_timeout = next_timer_deadline_ == NO_DEADLINE ||
                   next_timer_deadline_ <= now
                      ? NO_DEADLINE
                      : next_timer_deadline_ - now;
  }
  *timeout = SetNextDeadlineAndReturnTimeoutVal(now,
                                                active_gesture_deadline_,
                                                next_timeout);
}

void HapticButtonGeneratorFilterInterpreter::ConsumeGesture(
    const Gesture& gesture) {
  if (!enabled_.val_ || !is_haptic_pad_) {
    ProduceGesture(gesture);
    return;
  }

  // Determine if there is an active non-click multi-finger gesture.
  switch (gesture.type) {
    case kGestureTypeScroll:
    case kGestureTypeSwipe:
    case kGestureTypeFourFingerSwipe:
      active_gesture_ = true;
      break;
    case kGestureTypeFling:
    case kGestureTypeSwipeLift:
    case kGestureTypeFourFingerSwipeLift:
      active_gesture_ = false;
      break;
    case kGestureTypePinch:
      active_gesture_ = (gesture.details.pinch.zoom_state != GESTURES_ZOOM_END);
      break;
    default:
      break;
  }
  if (active_gesture_) {
    active_gesture_deadline_ = gesture.end_time + active_gesture_timeout_;
  }

  // When dragging while clicking, users often reduce the force applied, causing
  // accidental release. So we calculate a scaling factor to reduce the "up"
  // threshold which starts at 1.0 (normal threshold) for stationary fingers,
  // and goes down to 0.0 at the complete_release_suppress_speed_.
  if (gesture.type == kGestureTypeMove) {
    float distance_sq = gesture.details.move.dx * gesture.details.move.dx +
        gesture.details.move.dy * gesture.details.move.dy;
    stime_t time_delta = gesture.end_time - gesture.start_time;
    float complete_suppress_dist =
        complete_release_suppress_speed_.val_ * time_delta;
    float complete_suppress_dist_sq =
        complete_suppress_dist * complete_suppress_dist;

    release_suppress_factor_ =
        time_delta <= 0.0 ? 1.0 : 1.0 - distance_sq / complete_suppress_dist_sq;

    // Always allow release at very low force, to prevent a stuck button when
    // the user lifts their finger while moving quickly.
    release_suppress_factor_ = fmax(release_suppress_factor_, 0.1);
  }

  ProduceGesture(gesture);
}

}  // namespace gestures