Merge various clean-ups from upstream

The only functionality change here is to mouse scroll handling, which
won't affect Android as we don't use the library for mice. The other
changes are assorted cleanups including for variable-length array
issues, address sanitizer crashes, and various other small code issues.

Bug: 314743031
Test: Treehugger
Change-Id: I8655f873a13a9ba3b8ef437246c0c368f8bd1b76
diff --git a/METADATA b/METADATA
index c2fe6b5..4ab2fc3 100644
--- a/METADATA
+++ b/METADATA
@@ -6,7 +6,7 @@
     type: GIT
     value: "https://chromium.googlesource.com/chromiumos/platform/gestures/"
   }
-  version: "b31ef0adcaff61c045e95158cb9720543b42b531"
-  last_upgrade_date { year: 2023 month: 12 day: 13  }
+  version: "b824af1e782a4e741fbfe4b0c311de3bc161e2fc"
+  last_upgrade_date { year: 2024 month: 3 day: 13  }
   license_type: NOTICE
 }
diff --git a/Makefile b/Makefile
index d04ce98..9adcf1e 100644
--- a/Makefile
+++ b/Makefile
@@ -129,6 +129,15 @@
 	-DGESTURES_INTERNAL=1 \
 	-I.
 
+ifeq (yes,$(SANITIZE_GESTURES))
+CXXFLAGS+=\
+	-fsanitize=address,undefined \
+	-fno-sanitize-recover=all
+LINK_FLAGS+=\
+	-fsanitize=address,undefined \
+	-fno-sanitize-recover=all
+endif
+
 # Local compilation needs these flags, esp for code coverage testing
 ifeq (g++,$(CXX))
 CXXFLAGS+=\
@@ -138,9 +147,6 @@
 	-ftest-coverage \
 	-fprofile-arcs
 LINK_FLAGS+=-lgcov
-else
-CXXFLAGS+=\
-	-DXLOGGING
 endif
 
 PKG_CONFIG ?= pkg-config
diff --git a/include/finger_metrics.h b/include/finger_metrics.h
index 1773a94..c1f33dd 100644
--- a/include/finger_metrics.h
+++ b/include/finger_metrics.h
@@ -6,10 +6,10 @@
 #define GESTURES_FINGER_METRICS_H_
 
 #include <cmath>
+#include <vector>
 
 #include "include/gestures.h"
 #include "include/prop_registry.h"
-#include "include/vector.h"
 
 namespace gestures {
 
@@ -130,7 +130,7 @@
 
   // A collection of FingerMetrics describing the current hardware state.
   // The collection is sorted to yield the oldest finger first.
-  vector<FingerMetrics, kMaxFingers>& fingers() { return fingers_; }
+  std::vector<FingerMetrics>& fingers() { return fingers_; }
 
   // Find a FingerMetrics instance by it's tracking id.
   // Returns nullptr if not found.
@@ -156,7 +156,7 @@
   void SetFingerOriginTimestampForTesting(short tracking_id, stime_t time);
 
  private:
-  vector<FingerMetrics, kMaxFingers> fingers_;
+  std::vector<FingerMetrics> fingers_;
 
   MetricsProperties* properties_;
   std::unique_ptr<MetricsProperties> own_properties_;
diff --git a/include/immediate_interpreter.h b/include/immediate_interpreter.h
index 1be420e..7f2aa52 100644
--- a/include/immediate_interpreter.h
+++ b/include/immediate_interpreter.h
@@ -13,6 +13,7 @@
 #include "include/macros.h"
 #include "include/prop_registry.h"
 #include "include/tracer.h"
+#include "include/vector.h"
 
 #ifndef GESTURES_IMMEDIATE_INTERPRETER_H_
 #define GESTURES_IMMEDIATE_INTERPRETER_H_
diff --git a/include/mouse_interpreter.h b/include/mouse_interpreter.h
index e56be37..d1e12a3 100644
--- a/include/mouse_interpreter.h
+++ b/include/mouse_interpreter.h
@@ -45,9 +45,9 @@
   bool EmulateScrollWheel(const HardwareState& hwstate);
  private:
   struct WheelRecord {
-    WheelRecord(float v, stime_t t): value(v), timestamp(t) {}
-    WheelRecord(): value(0), timestamp(0) {}
-    float value;
+    WheelRecord(float v, stime_t t): change(v), timestamp(t) {}
+    WheelRecord(): change(0), timestamp(0) {}
+    float change;
     stime_t timestamp;
   };
 
@@ -60,8 +60,8 @@
 
   HardwareState prev_state_;
 
-  // Records last scroll wheel event.
-  WheelRecord last_wheel_, last_hwheel_;
+  // Records last scroll wheel events.
+  std::vector<WheelRecord> last_vertical_wheels_, last_horizontal_wheels_;
 
   // Accumulators to measure scroll distance while doing scroll wheel emulation
   double wheel_emulation_accu_x_;
@@ -85,6 +85,13 @@
   // Enable high-resolution scrolling.
   BoolProperty hi_res_scrolling_;
 
+  // When calculating scroll velocity for the purpose of acceleration, we
+  // use the average of this many events in the same direction. This is to avoid
+  // over-accelerating if we receive batched events with timestamps that are
+  // artificially close. If we don't have enough events, we won't accelerate at
+  // all.
+  IntProperty scroll_velocity_buffer_size_;
+
   // We use normal CDF to simulate scroll wheel acceleration curve. Use the
   // following method to generate the coefficients of a degree-4 polynomial
   // regression for a specific normal cdf in Python.
diff --git a/include/string_util.h b/include/string_util.h
index 6e1a4ba..67fda6c 100644
--- a/include/string_util.h
+++ b/include/string_util.h
@@ -21,36 +21,9 @@
 void StringAppendV(std::string* dst, const char* format, va_list ap)
     PRINTF_FORMAT(2, 0);
 
-// Trims any whitespace from either end of the input string.  Returns where
-// whitespace was found.
-// The non-wide version has two functions:
-// * TrimWhitespaceASCII()
-//   This function is for ASCII strings and only looks for ASCII whitespace;
-// Please choose the best one according to your usage.
-// NOTE: Safe to use the same variable for both input and output.
-enum TrimPositions {
-  TRIM_NONE     = 0,
-  TRIM_LEADING  = 1 << 0,
-  TRIM_TRAILING = 1 << 1,
-  TRIM_ALL      = TRIM_LEADING | TRIM_TRAILING,
-};
-TrimPositions TrimWhitespaceASCII(const std::string& input,
-                                              TrimPositions positions,
-                                              std::string* output);
-
-// Returns true if str starts with search, or false otherwise.
-bool StartsWithASCII(const std::string& str,
-                     const std::string& search,
-                     bool case_sensitive);
-
-// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which
-// the trailing byte of a multi-byte character can be in the ASCII range.
-// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK.
-// Note: |c| must be in the ASCII range.
-void SplitString(const std::string& str,
-                 char c,
-                 std::vector<std::string>* r);
-
+// Trims whitespace from the start and end of the input string.  This function
+// is for ASCII strings and only looks for ASCII whitespace.
+std::string TrimWhitespaceASCII(const std::string& input);
 
 }  // namespace gestures
 
diff --git a/include/util.h b/include/util.h
index f0c2a39..9371327 100644
--- a/include/util.h
+++ b/include/util.h
@@ -8,6 +8,7 @@
 #include <list>
 #include <map>
 #include <set>
+#include <vector>
 
 #include <math.h>
 
@@ -78,14 +79,14 @@
 static inline
 void RemoveMissingIdsFromSet(std::set<short>* the_set,
                              const HardwareState& hs) {
-  short old_ids[the_set->size() + 1];
-  size_t old_ids_len = 0;
+  std::vector<short> old_ids;
+  old_ids.reserve(the_set->size() + 1);
   for (typename std::set<short>::const_iterator it = the_set->begin();
        it != the_set->end(); ++it)
     if (!hs.GetFingerState(*it))
-      old_ids[old_ids_len++] = *it;
-  for (size_t i = 0; i < old_ids_len; i++)
-    the_set->erase(old_ids[i]);
+      old_ids.push_back(*it);
+  for (auto id : old_ids)
+    the_set->erase(id);
 }
 
 template<typename Set, typename Elt>
diff --git a/src/activity_log.cc b/src/activity_log.cc
index 9e6bfbf..d668f9c 100644
--- a/src/activity_log.cc
+++ b/src/activity_log.cc
@@ -579,7 +579,7 @@
   string gestures_version = VCSID;
 
   // Strip tailing whitespace.
-  TrimWhitespaceASCII(gestures_version, TRIM_ALL, &gestures_version);
+  gestures_version = TrimWhitespaceASCII(gestures_version);
   (*root)["gesturesVersion"] = Json::Value(gestures_version);
   (*root)[kKeyProperties] = EncodePropRegistry();
 }
diff --git a/src/activity_replay_unittest.cc b/src/activity_replay_unittest.cc
index ef75491..0dd2879 100644
--- a/src/activity_replay_unittest.cc
+++ b/src/activity_replay_unittest.cc
@@ -20,6 +20,42 @@
 
 namespace gestures {
 
+namespace {
+
+template <typename STR>
+void SplitStringT(const STR& str,
+                  const typename STR::value_type s,
+                  bool trim_whitespace,
+                  std::vector<STR>* r) {
+  r->clear();
+  size_t last = 0;
+  size_t c = str.size();
+  for (size_t i = 0; i <= c; ++i) {
+    if (i == c || str[i] == s) {
+      STR tmp(str, last, i - last);
+      if (trim_whitespace)
+        tmp = TrimWhitespaceASCII(tmp);
+      // Avoid converting an empty or all-whitespace source string into a vector
+      // of one empty string.
+      if (i != c || !r->empty() || !tmp.empty())
+        r->push_back(tmp);
+      last = i + 1;
+    }
+  }
+}
+
+// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which
+// the trailing byte of a multi-byte character can be in the ASCII range.
+// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK.
+// Note: |c| must be in the ASCII range.
+void SplitString(const std::string& str,
+                 char c,
+                 std::vector<std::string>* r) {
+  SplitStringT(str, c, true, r);
+}
+
+}  // namespace
+
 class ActivityReplayTest : public ::testing::Test {};
 
 // This test reads a log file and replays it. This test should be enabled for a
diff --git a/src/command_line.cc b/src/command_line.cc
index 7f309a2..2779b13 100644
--- a/src/command_line.cc
+++ b/src/command_line.cc
@@ -59,7 +59,7 @@
   bool parse_switches = true;
   for (size_t i = 1; i < argv.size(); ++i) {
     std::string arg = argv[i];
-    TrimWhitespaceASCII(arg, TRIM_ALL, &arg);
+    arg = TrimWhitespaceASCII(arg);
 
     std::string switch_string;
     std::string switch_value;
@@ -131,7 +131,7 @@
 }
 
 void CommandLine::SetProgram(const std::string& program) {
-  TrimWhitespaceASCII(program, TRIM_ALL, &argv_[0]);
+  argv_[0] = TrimWhitespaceASCII(program);
 }
 
 bool CommandLine::HasSwitch(const std::string& switch_string) const {
diff --git a/src/finger_metrics.cc b/src/finger_metrics.cc
index a79ea10..4215196 100644
--- a/src/finger_metrics.cc
+++ b/src/finger_metrics.cc
@@ -76,14 +76,17 @@
          < vert_axis_sq * horiz_axis_sq;
 }
 
-Metrics::Metrics(MetricsProperties* properties) : properties_(properties) {}
+Metrics::Metrics(MetricsProperties* properties) : properties_(properties) {
+  fingers_.reserve(kMaxFingers);
+}
 
 const FingerMetrics* Metrics::GetFinger(short tracking_id) const {
-  auto iter = fingers_.find(FingerMetrics(tracking_id));
-  if (iter != fingers_.end())
-    return iter;
-  else
-    return nullptr;
+  for (auto iter = fingers_.cbegin(); iter != fingers_.cend(); ++iter) {
+    if(iter->tracking_id() == tracking_id) {
+      return &(*iter);
+    }
+  }
+  return nullptr;
 }
 
 const FingerMetrics* Metrics::GetFinger(const FingerState& state) const {
@@ -98,8 +101,7 @@
   // create metrics for new fingers
   for (int i=0; i<hwstate.finger_cnt; ++i) {
     const FingerState& state = hwstate.fingers[i];
-    auto iter = fingers_.find(FingerMetrics(state.tracking_id));
-    if (iter == fingers_.end()) {
+    if (GetFinger(state.tracking_id) == nullptr) {
       fingers_.push_back(FingerMetrics(state,
                                        hwstate.timestamp));
       ++new_count;
@@ -138,9 +140,11 @@
 
 void Metrics::SetFingerOriginTimestampForTesting(short tracking_id,
                                                  stime_t time) {
-  if (auto iter = fingers_.find(FingerMetrics(tracking_id));
-      iter != fingers_.end()) {
-    fingers_.erase(iter);
+  for (auto iter = fingers_.begin(); iter != fingers_.end(); ++iter) {
+    if(iter->tracking_id() == tracking_id) {
+      fingers_.erase(iter);
+      break;
+    }
   }
   fingers_.push_back(FingerMetrics(tracking_id, time));
 }
diff --git a/src/gestures.cc b/src/gestures.cc
index b4d5271..cd49c68 100644
--- a/src/gestures.cc
+++ b/src/gestures.cc
@@ -42,7 +42,6 @@
 using std::string;
 using std::min;
 using gestures::StringPrintf;
-using gestures::StartsWithASCII;
 
 // C API:
 
@@ -135,9 +134,9 @@
   if (flags) {
     // prepend remaining number
     ret = StringPrintf("%u%s", flags, ret.c_str());
-  } else if (StartsWithASCII(ret, kPipeSeparator, false)) {
+  } else if (ret.rfind(kPipeSeparator, 0) == 0) {
     // strip extra pipe
-    ret = string(ret.c_str() + strlen(kPipeSeparator));
+    ret = ret.substr(strlen(kPipeSeparator));
   } else {
     ret = "0";
   }
@@ -507,7 +506,7 @@
     interpret_timer_ = nullptr;
   }
   if (interpret_timer_)
-    Log("How was interpret_timer_ not null?!");
+    Err("How was interpret_timer_ not null?!");
   timer_provider_ = tp;
   timer_provider_data_ = data;
   if (timer_provider_)
diff --git a/src/gestures_unittest.cc b/src/gestures_unittest.cc
index 6b00902..b11a339 100644
--- a/src/gestures_unittest.cc
+++ b/src/gestures_unittest.cc
@@ -411,6 +411,19 @@
   EXPECT_DOUBLE_EQ(2000000000.999999999, StimeFromTimespec(&tv));
 }
 
+TEST(GesturesTest, FingerStateFlagsStringTest) {
+  EXPECT_EQ("0", FingerState::FlagsString(0));
+  EXPECT_EQ("GESTURES_FINGER_PALM",
+            FingerState::FlagsString(GESTURES_FINGER_PALM));
+  EXPECT_EQ("GESTURES_FINGER_PALM | GESTURES_FINGER_WARP_X_MOVE",
+            FingerState::FlagsString(
+                GESTURES_FINGER_PALM | GESTURES_FINGER_WARP_X_MOVE));
+  // 1 << 31 probably won't be used as a finger flag value anytime soon, so use
+  // it to test prepending the remaining number.
+  EXPECT_EQ("2147483648 | GESTURES_FINGER_PALM",
+            FingerState::FlagsString(GESTURES_FINGER_PALM | (1 << 31)));
+}
+
 TEST(GesturesTest, HardwareStateGetFingerStateTest) {
   FingerState fs[] = {
     { 0, 0, 0, 0, 1, 0, 150, 4000, 4, 0 },
diff --git a/src/iir_filter_interpreter.cc b/src/iir_filter_interpreter.cc
index 5ea513d..84a1faf 100644
--- a/src/iir_filter_interpreter.cc
+++ b/src/iir_filter_interpreter.cc
@@ -5,6 +5,7 @@
 #include "include/iir_filter_interpreter.h"
 
 #include <utility>
+#include <vector>
 
 namespace gestures {
 
@@ -57,14 +58,15 @@
   LogHardwareStatePre(name, hwstate);
 
   // Delete old entries from map
-  short dead_ids[histories_.size() + 1];
-  size_t dead_ids_len = 0;
+  std::vector<short> dead_ids;
+  dead_ids.reserve(histories_.size());
+
   for (std::map<short, IoHistory>::iterator it = histories_.begin(),
            e = histories_.end(); it != e; ++it)
     if (!hwstate.GetFingerState((*it).first))
-      dead_ids[dead_ids_len++] = (*it).first;
-  for (size_t i = 0; i < dead_ids_len; ++i)
-    histories_.erase(dead_ids[i]);
+      dead_ids.push_back((*it).first);
+  for (auto dead_id : dead_ids)
+    histories_.erase(dead_id);
 
   // Modify current hwstate
   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
diff --git a/src/immediate_interpreter.cc b/src/immediate_interpreter.cc
index c1aa568..9f63427 100644
--- a/src/immediate_interpreter.cc
+++ b/src/immediate_interpreter.cc
@@ -11,6 +11,7 @@
 #include <functional>
 #include <limits>
 #include <tuple>
+#include <vector>
 
 #include "include/gestures.h"
 #include "include/logging.h"
@@ -61,9 +62,7 @@
   // New finger must be close enough to an existing finger
   if (!touched_.empty()) {
     bool reject_new_finger = true;
-    for (std::map<short, FingerState>::const_iterator it =
-             touched_.begin(), e = touched_.end(); it != e; ++it) {
-      const FingerState& existing_fs = (*it).second;
+    for (const auto& [tracking_id, existing_fs] : touched_) {
       if (immediate_interpreter_->metrics_->CloseEnoughToGesture(
               Vector2(existing_fs),
               Vector2(fs))) {
@@ -113,28 +112,26 @@
     else if (diff < 0)
       t5r2_released_size_ += -diff;
   }
-  for (std::set<short>::const_iterator it = added.begin(),
-           e = added.end(); it != e; ++it)
-    Log("TapRecord::Update: Added: %d", *it);
-  for (std::set<short>::const_iterator it = removed.begin(),
-           e = removed.end(); it != e; ++it)
-    Log("TapRecord::Update: Removed: %d", *it);
-  for (std::set<short>::const_iterator it = dead.begin(),
-           e = dead.end(); it != e; ++it)
-    Log("TapRecord::Update: Dead: %d", *it);
+  for (short tracking_id : added) {
+    Log("TapRecord::Update: Added: %d", tracking_id);
+  }
+  for (short tracking_id: removed) {
+    Log("TapRecord::Update: Removed: %d", tracking_id);
+  }
+  for (short tracking_id : dead) {
+    Log("TapRecord::Update: Dead: %d", tracking_id);
+  }
   for_each(dead.begin(), dead.end(),
            bind(&TapRecord::Remove, this, std::placeholders::_1));
-  for (std::set<short>::const_iterator it = added.begin(),
-           e = added.end(); it != e; ++it)
-    NoteTouch(*it, *hwstate.GetFingerState(*it));
+  for (short tracking_id : added) {
+    NoteTouch(tracking_id, *hwstate.GetFingerState(tracking_id));
+  }
   for_each(removed.begin(), removed.end(),
            bind(&TapRecord::NoteRelease, this, std::placeholders::_1));
   // Check if min tap/cotap pressure met yet
   const float cotap_min_pressure = CotapMinPressure();
-  for (std::map<short, FingerState>::iterator it =
-           touched_.begin(), e = touched_.end();
-       it != e; ++it) {
-    const FingerState* fs = hwstate.GetFingerState((*it).first);
+  for (auto& [tracking_id, existing_fs] : touched_) {
+    const FingerState* fs = hwstate.GetFingerState(tracking_id);
     if (fs) {
       if (fs->pressure >= immediate_interpreter_->tap_min_pressure() ||
           !immediate_interpreter_->device_reports_pressure())
@@ -142,11 +139,11 @@
       if (fs->pressure >= cotap_min_pressure ||
           !immediate_interpreter_->device_reports_pressure()) {
         min_cotap_pressure_met_.insert(fs->tracking_id);
-        if ((*it).second.pressure < cotap_min_pressure &&
+        if (existing_fs.pressure < cotap_min_pressure &&
             immediate_interpreter_->device_reports_pressure()) {
           // Update existing record, since the old one hadn't met the cotap
           // pressure
-          (*it).second = *fs;
+          existing_fs = *fs;
         }
       }
       stime_t finger_age = hwstate.timestamp -
@@ -171,20 +168,19 @@
 bool TapRecord::Moving(const HardwareState& hwstate,
                        const float dist_max) const {
   const float cotap_min_pressure = CotapMinPressure();
-  for (std::map<short, FingerState>::const_iterator it =
-           touched_.begin(), e = touched_.end(); it != e; ++it) {
-    const FingerState* fs = hwstate.GetFingerState((*it).first);
+  for (const auto& [tracking_id, existing_fs] : touched_) {
+    const FingerState* fs = hwstate.GetFingerState(tracking_id);
     if (!fs)
       continue;
     // Only look for moving when current frame meets cotap pressure and
     // our history contains a contact that's met cotap pressure.
     if ((fs->pressure < cotap_min_pressure ||
-        (*it).second.pressure < cotap_min_pressure) &&
+        existing_fs.pressure < cotap_min_pressure) &&
         immediate_interpreter_->device_reports_pressure())
       continue;
     // Compute distance moved
-    float dist_x = fs->position_x - (*it).second.position_x;
-    float dist_y = fs->position_y - (*it).second.position_y;
+    float dist_x = fs->position_x - existing_fs.position_x;
+    float dist_y = fs->position_y - existing_fs.position_y;
     // Respect WARP flags
     if (fs->flags & GESTURES_FINGER_WARP_X_TAP_MOVE)
       dist_x = 0.0;
@@ -202,10 +198,9 @@
 bool TapRecord::Motionless(const HardwareState& hwstate, const HardwareState&
                            prev_hwstate, const float max_speed) const {
   const float cotap_min_pressure = CotapMinPressure();
-  for (std::map<short, FingerState>::const_iterator it =
-           touched_.begin(), e = touched_.end(); it != e; ++it) {
-    const FingerState* fs = hwstate.GetFingerState((*it).first);
-    const FingerState* prev_fs = prev_hwstate.GetFingerState((*it).first);
+  for (const auto& [tracking_id, _] : touched_) {
+    const FingerState* fs = hwstate.GetFingerState(tracking_id);
+    const FingerState* prev_fs = prev_hwstate.GetFingerState(tracking_id);
     if (!fs || !prev_fs)
       continue;
     // Only look for moving when current frame meets cotap pressure and
@@ -233,12 +228,12 @@
     ret = t5r2_touched_size_ && t5r2_touched_size_ == t5r2_released_size_;
   else
     ret = !touched_.empty() && (touched_.size() == released_.size());
-  for (std::map<short, FingerState>::const_iterator
-           it = touched_.begin(), e = touched_.end(); it != e; ++it)
-    Log("TapRecord::TapComplete: touched_: %d", (*it).first);
-  for (std::set<short>::const_iterator it = released_.begin(),
-           e = released_.end(); it != e; ++it)
-    Log("TapRecord::TapComplete: released_: %d", *it);
+  for (const auto& [tracking_id, finger_state] : touched_) {
+    Log("TapRecord::TapComplete: touched_: %d", tracking_id);
+  }
+  for (short tracking_id : released_) {
+    Log("TapRecord::TapComplete: released_: %d", tracking_id);
+  }
   return ret;
 }
 
@@ -457,10 +452,9 @@
   float dy = 0.0;
   bool stationary = true;
   bool pressure_changing = false;
-  for (FingerMap::const_iterator it =
-           gs_fingers.begin(), e = gs_fingers.end(); it != e; ++it) {
-    const FingerState* fs = state_buffer.Get(0).GetFingerState(*it);
-    const FingerState* prev = state_buffer.Get(1).GetFingerState(*it);
+  for (short tracking_id : gs_fingers) {
+    const FingerState* fs = state_buffer.Get(0).GetFingerState(tracking_id);
+    const FingerState* prev = state_buffer.Get(1).GetFingerState(tracking_id);
     if (!prev)
       return false;
     const stime_t dt =
@@ -1687,9 +1681,9 @@
       thumb_eval_timer_[fs.tracking_id] = thumb_eval_timeout_.val_;
     }
   }
-  for (std::map<short, stime_t>::const_iterator it = thumb_.begin();
-       it != thumb_.end(); ++it)
-    pointing_.erase((*it).first);
+  for (const auto& [tracking_id, _] : thumb_) {
+    pointing_.erase(tracking_id);
+  }
 }
 
 void ImmediateInterpreter::UpdateNonGsFingers(const HardwareState& hwstate) {
@@ -1733,7 +1727,7 @@
     return {};
   }
 
-  const FingerState* fs[hwstate.finger_cnt];
+  std::vector<FingerState*> fs(hwstate.finger_cnt);
   for (size_t i = 0; i < hwstate.finger_cnt; ++i)
     fs[i] = &hwstate.fingers[i];
 
@@ -1743,11 +1737,12 @@
   FingerMap ret;
   size_t sorted_cnt;
   if (hwstate.finger_cnt > kMaxGesturingFingers) {
-    std::partial_sort(fs, fs + kMaxGesturingFingers, fs + hwstate.finger_cnt,
+    std::partial_sort(fs.begin(), fs.begin() + kMaxGesturingFingers,
+                      fs.end(),
                       compare);
     sorted_cnt = kMaxGesturingFingers;
   } else {
-    std::sort(fs, fs + hwstate.finger_cnt, compare);
+    std::sort(fs.begin(), fs.end(), compare);
     sorted_cnt = hwstate.finger_cnt;
   }
   for (size_t i = 0; i < sorted_cnt; i++)
@@ -1883,9 +1878,7 @@
             }
             if (current_gesture_type_ != kGestureTypeNull) {
               active_gs_fingers->clear();
-              for (vector<short, kMaxGesturingFingers>::const_iterator it =
-                   sorted_ids.begin(), e = sorted_ids.end(); it != e; ++it)
-                active_gs_fingers->insert(*it);
+              active_gs_fingers->insert(sorted_ids.begin(), sorted_ids.end());
               break;
             }
           }
@@ -1975,8 +1968,10 @@
   // that until we have enough points
   size_t dist_sq_capacity =
       (finger_ids.size() * (finger_ids.size() - 1)) / 2;
-  DistSqElt dist_sq[dist_sq_capacity];
-  size_t dist_sq_len = 0;
+
+  std::vector<DistSqElt> dist_sq;
+  dist_sq.reserve(dist_sq_capacity);
+
   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
     const FingerState& fs1 = hwstate.fingers[i];
     if (!SetContainsValue(finger_ids, fs1.tracking_id))
@@ -1989,24 +1984,20 @@
         DistSq(fs1, fs2),
         { fs1.tracking_id, fs2.tracking_id }
       };
-      if (dist_sq_len >= dist_sq_capacity) {
-        Err("%s: Array overrun", __func__);
-        break;
-      }
-      dist_sq[dist_sq_len++] = elt;
+      dist_sq.push_back(elt);
     }
   }
 
   DistSqCompare distSqCompare;
-  std::sort(dist_sq, dist_sq + dist_sq_len, distSqCompare);
+  std::sort(dist_sq.begin(), dist_sq.end(), distSqCompare);
 
   if (out_sorted_ids == nullptr) {
     Err("out_sorted_ids became null");
     return;
   }
-  for (size_t i = 0; i < dist_sq_len; i++) {
-    short id1 = dist_sq[i].tracking_id[0];
-    short id2 = dist_sq[i].tracking_id[1];
+  for (auto const & d: dist_sq) {
+    short id1 = d.tracking_id[0];
+    short id2 = d.tracking_id[1];
     bool contains1 = out_sorted_ids->find(id1) != out_sorted_ids->end();
     bool contains2 = out_sorted_ids->find(id2) != out_sorted_ids->end();
     if (contains1 == contains2 && !out_sorted_ids->empty()) {
@@ -2222,9 +2213,8 @@
 bool ImmediateInterpreter::IsTooCloseToThumb(const FingerState& finger) const {
   const float kMin2fDistThreshSq = tapping_finger_min_separation_.val_ *
       tapping_finger_min_separation_.val_;
-  for (std::map<short, stime_t>::const_iterator it = thumb_.begin();
-       it != thumb_.end(); ++it) {
-    const FingerState* thumb = state_buffer_.Get(0).GetFingerState(it->first);
+  for (const auto& [tracking_id, _] : thumb_) {
+    const FingerState* thumb = state_buffer_.Get(0).GetFingerState(tracking_id);
     float xdist = fabsf(finger.position_x - thumb->position_x);
     float ydist = fabsf(finger.position_y - thumb->position_y);
     if (xdist * xdist + ydist * ydist < kMin2fDistThreshSq)
@@ -2463,8 +2453,10 @@
     return kGestureTypeNull;
   }
 
-  const FingerState* x_fingers[num_fingers];
-  const FingerState* y_fingers[num_fingers];
+  assert(num_fingers <= (int) kMaxGesturingFingers);
+
+  const FingerState* x_fingers[kMaxGesturingFingers];
+  const FingerState* y_fingers[kMaxGesturingFingers];
   for (int i = 0; i < num_fingers; i++) {
     x_fingers[i] = fingers[i];
     y_fingers[i] = fingers[i];
@@ -2478,13 +2470,13 @@
   bool horizontal =
       (x_fingers[num_fingers - 1]->position_x - x_fingers[0]->position_x) >=
       (y_fingers[num_fingers -1]->position_y - y_fingers[0]->position_y);
-  const FingerState* sorted_fingers[num_fingers];
+  const FingerState* sorted_fingers[4];
   for (int i = 0; i < num_fingers; i++) {
     sorted_fingers[i] = horizontal ? x_fingers[i] : y_fingers[i];
   }
 
-  float dx[num_fingers];
-  float dy[num_fingers];
+  float dx[kMaxGesturingFingers];
+  float dy[kMaxGesturingFingers];
   float dy_sum = 0;
   float dx_sum = 0;
   for (int i = 0; i < num_fingers; i++) {
@@ -2548,7 +2540,7 @@
     case kTtcDragRelease: return tap_drag_timeout_.val_;
     case kTtcDragRetouch: return tap_timeout_.val_;
     default:
-      Log("Unknown state!");
+      Err("Unknown TapToClickState %u!", state);
       return 0.0;
   }
 }
@@ -2606,14 +2598,13 @@
           (GESTURES_FINGER_NO_TAP | GESTURES_FINGER_MERGE))
         cancel_tapping = true;
     }
-    for (FingerMap::const_iterator it =
-             gs_fingers.begin(), e = gs_fingers.end(); it != e; ++it) {
-      const FingerState* fs = hwstate->GetFingerState(*it);
+    for (short tracking_id : gs_fingers) {
+      const FingerState* fs = hwstate->GetFingerState(tracking_id);
       if (!fs) {
         Err("Missing finger state?!");
         continue;
       }
-      tap_gs_fingers.insert(*it);
+      tap_gs_fingers.insert(tracking_id);
     }
   }
   std::set<short> added_fingers;
@@ -2638,40 +2629,37 @@
 
   if (hwstate && (!same_fingers || prev_tap_gs_fingers_ != tap_gs_fingers)) {
     // See if fingers were added
-    for (FingerMap::const_iterator it =
-             tap_gs_fingers.begin(), e = tap_gs_fingers.end(); it != e; ++it) {
+    for (short tracking_id : tap_gs_fingers) {
       // If the finger was marked as a thumb before, it is not new.
-      if (hwstate->timestamp - finger_origin_timestamp(*it) >
+      if (hwstate->timestamp - finger_origin_timestamp(tracking_id) >
                thumb_click_prevention_timeout_.val_)
         continue;
 
-      if (!SetContainsValue(prev_tap_gs_fingers_, *it)) {
+      if (!SetContainsValue(prev_tap_gs_fingers_, tracking_id)) {
         // Gesturing finger wasn't in prev state. It's new.
-        const FingerState* fs = hwstate->GetFingerState(*it);
+        const FingerState* fs = hwstate->GetFingerState(tracking_id);
         if (FingerTooCloseToTap(*hwstate, *fs) ||
             FingerTooCloseToTap(state_buffer_.Get(1), *fs) ||
             SetContainsValue(tap_dead_fingers_, fs->tracking_id))
           continue;
-        added_fingers.insert(*it);
-        Log("TTC: Added %d", *it);
+        added_fingers.insert(tracking_id);
+        Log("TTC: Added %d", tracking_id);
       }
     }
 
     // See if fingers were removed or are now non-gesturing (dead)
-    for (FingerMap::const_iterator it =
-             prev_tap_gs_fingers_.begin(), e = prev_tap_gs_fingers_.end();
-         it != e; ++it) {
-      if (tap_gs_fingers.find(*it) != tap_gs_fingers.end())
+    for (short tracking_id : prev_tap_gs_fingers_) {
+      if (tap_gs_fingers.find(tracking_id) != tap_gs_fingers.end())
         // still gesturing; neither removed nor dead
         continue;
-      if (!hwstate->GetFingerState(*it)) {
+      if (!hwstate->GetFingerState(tracking_id)) {
         // Previously gesturing finger isn't in current state. It's gone.
-        removed_fingers.insert(*it);
-        Log("TTC: Removed %d", *it);
+        removed_fingers.insert(tracking_id);
+        Log("TTC: Removed %d", tracking_id);
       } else {
         // Previously gesturing finger is in current state. It's dead.
-        dead_fingers.insert(*it);
-        Log("TTC: Dead %d", *it);
+        dead_fingers.insert(tracking_id);
+        Log("TTC: Dead %d", tracking_id);
       }
     }
   }
@@ -2747,7 +2735,7 @@
         break;
       }
       if (!hwstate) {
-        Log("hwstate is null but no timeout?!");
+        Err("hwstate is null but not a timeout?!");
         break;
       }
       tap_record_.Update(
@@ -2797,7 +2785,7 @@
       break;
     case kTtcSubsequentTapBegan:
       if (!is_timeout && !hwstate) {
-        Log("hwstate is null but not a timeout?!");
+        Err("hwstate is null but not a timeout?!");
         break;
       }
       if (hwstate)
@@ -2901,7 +2889,7 @@
         break;
       }
       if (!hwstate) {
-        Log("not timeout but hwstate is null?!");
+        Err("hwstate is null but not a timeout?!");
         break;
       }
       if (tap_record_.Moving(*hwstate, tap_move_dist_.val_))
@@ -3038,15 +3026,15 @@
     const FingerMap& gs_fingers,
     const FingerMap& newly_moving_fingers) {
   // Update started moving time if any gesturing finger is newly moving.
-  for (auto it = gs_fingers.begin(), e = gs_fingers.end(); it != e; ++it) {
-    if (SetContainsValue(newly_moving_fingers, *it)) {
+  for (short gs_tracking_id : gs_fingers) {
+    if (SetContainsValue(newly_moving_fingers, gs_tracking_id)) {
       started_moving_time_ = now;
       // Extend the thumb evaluation period for any finger that is still under
       // evaluation as there is a new moving finger.
-      for (std::map<short, stime_t>::iterator it = thumb_.begin();
-           it != thumb_.end(); ++it)
-        if ((*it).second < thumb_eval_timeout_.val_ && (*it).second > 0.0)
-          (*it).second = thumb_eval_timeout_.val_;
+      for (auto& [_, time] : thumb_) {
+        if (time < thumb_eval_timeout_.val_ && time > 0.0)
+          time = thumb_eval_timeout_.val_;
+      }
       return;
     }
   }
@@ -3168,9 +3156,8 @@
       const HardwareState& prev_hs = state_buffer_.Get(1);
       if (!current) {
         float curr_dist_sq = -1;
-        for (FingerMap::const_iterator it =
-                 fingers.begin(), e = fingers.end(); it != e; ++it) {
-          const FingerState* fs = hwstate.GetFingerState(*it);
+        for (short tracking_id : fingers) {
+          const FingerState* fs = hwstate.GetFingerState(tracking_id);
           const FingerState* prev_fs = prev_hs.GetFingerState(fs->tracking_id);
           if (!prev_fs)
             break;
@@ -3277,9 +3264,8 @@
       float finger_cnt[] = { 0.0, 0.0 };
       float FingerState::*fields[] = { &FingerState::position_x,
                                        &FingerState::position_y };
-      for (FingerMap::const_iterator it =
-               fingers.begin(), e = fingers.end(); it != e; ++it) {
-        if (!state_buffer_.Get(1).GetFingerState(*it)) {
+      for (short tracking_id : fingers) {
+        if (!state_buffer_.Get(1).GetFingerState(tracking_id)) {
           Err("missing prev state?");
           continue;
         }
@@ -3290,8 +3276,8 @@
           if (!valid[i] || !correct_axis)
             continue;
           float FingerState::*field = fields[i];
-          float delta = hwstate.GetFingerState(*it)->*field -
-              state_buffer_.Get(1).GetFingerState(*it)->*field;
+          float delta = hwstate.GetFingerState(tracking_id)->*field -
+              state_buffer_.Get(1).GetFingerState(tracking_id)->*field;
           // The multiply is to see if they have the same sign:
           if (sum_delta[i] == 0.0 || sum_delta[i] * delta > 0) {
             sum_delta[i] += delta;
@@ -3430,9 +3416,8 @@
 
 bool AnyGesturingFingerLeft(const HardwareState& state,
                             const FingerMap& prev_gs_fingers) {
-  for (FingerMap::const_iterator it = prev_gs_fingers.begin(),
-                                 e = prev_gs_fingers.end(); it != e; ++it) {
-    if (!state.GetFingerState(*it)) {
+  for (short tracking_id : prev_gs_fingers) {
+    if (!state.GetFingerState(tracking_id)) {
       return true;
     }
   }
diff --git a/src/immediate_interpreter_unittest.cc b/src/immediate_interpreter_unittest.cc
index c7c6db2..994b11c 100644
--- a/src/immediate_interpreter_unittest.cc
+++ b/src/immediate_interpreter_unittest.cc
@@ -28,31 +28,31 @@
   EXPECT_EQ(22.0, ev3.dy);
   EXPECT_EQ(33.0, ev3.dt);
 
-  ScrollEventBuffer* evbuf = new ScrollEventBuffer(2);
-  evbuf->Insert(1.0, 2.0, 3.0);
-  ev1 = evbuf->Get(0);
+  ScrollEventBuffer evbuf(2);
+  evbuf.Insert(1.0, 2.0, 3.0);
+  ev1 = evbuf.Get(0);
   EXPECT_EQ(1.0, ev1.dx);
   EXPECT_EQ(2.0, ev1.dy);
   EXPECT_EQ(3.0, ev1.dt);
-  ev1 = evbuf->Get(3);
+  ev1 = evbuf.Get(3);
   EXPECT_EQ(0.0, ev1.dx);
   EXPECT_EQ(0.0, ev1.dy);
   EXPECT_EQ(0.0, ev1.dt);
 }
 
 TEST(ImmediateInterpreterTest, HardwareStateBufferTest) {
-  HardwareStateBuffer* hsb = new HardwareStateBuffer(10);
-  hsb->Reset(0);
-  EXPECT_EQ(hsb->Size(), 10);
+  HardwareStateBuffer hsb(10);
+  hsb.Reset(0);
+  EXPECT_EQ(hsb.Size(), 10);
 }
 
 TEST(ImmediateInterpreterTest, ScrollManagerTest) {
-  PropRegistry* my_prop_reg = new PropRegistry();
-  ScrollManager* sm = new ScrollManager(my_prop_reg);
-  ScrollEventBuffer* scroll_buffer = new ScrollEventBuffer(2);
+  PropRegistry my_prop_reg;
+  ScrollManager sm(&my_prop_reg);
+  ScrollEventBuffer scroll_buffer(2);
   ScrollEvent ev;
 
-  sm->RegressScrollVelocity(*scroll_buffer, 1, &ev);
+  sm.RegressScrollVelocity(scroll_buffer, 1, &ev);
   EXPECT_EQ(0.0, ev.dx);
   EXPECT_EQ(0.0, ev.dy);
   EXPECT_EQ(1.0, ev.dt);
diff --git a/src/interpreter_unittest.cc b/src/interpreter_unittest.cc
index e0d1cff..ff5e785 100644
--- a/src/interpreter_unittest.cc
+++ b/src/interpreter_unittest.cc
@@ -75,9 +75,8 @@
 
 TEST(InterpreterTest, SimpleTest) {
   PropRegistry prop_reg;
-  InterpreterTestInterpreter* base_interpreter =
-      new InterpreterTestInterpreter(&prop_reg);
-  base_interpreter->SetEventLoggingEnabled(true);
+  InterpreterTestInterpreter base_interpreter(&prop_reg);
+  base_interpreter.SetEventLoggingEnabled(true);
   MetricsProperties mprops(&prop_reg);
 
   HardwareProperties hwprops = {
@@ -94,19 +93,19 @@
     .is_haptic_pad = 0,
   };
 
-  TestInterpreterWrapper wrapper(base_interpreter, &hwprops);
+  TestInterpreterWrapper wrapper(&base_interpreter, &hwprops);
 
-  base_interpreter->bool_prop_.val_ = 1;
-  base_interpreter->double_prop_.val_ = 1;
-  base_interpreter->int_prop_.val_ = 1;
-  base_interpreter->string_prop_.val_ = "x";
+  base_interpreter.bool_prop_.val_ = 1;
+  base_interpreter.double_prop_.val_ = 1;
+  base_interpreter.int_prop_.val_ = 1;
+  base_interpreter.string_prop_.val_ = "x";
 
   //if (prop_reg)
-  //  prop_reg->set_activity_log(&(base_interpreter->log_));
+  //  prop_reg->set_activity_log(&(base_interpreter.log_));
 
   char interpreter_name[] = "InterpreterTestInterpreter";
-  base_interpreter->expected_interpreter_name_ = interpreter_name;
-  base_interpreter->return_value_ = Gesture(kGestureMove,
+  base_interpreter.expected_interpreter_name_ = interpreter_name;
+  base_interpreter.return_value_ = Gesture(kGestureMove,
                                             0,  // start time
                                             1,  // end time
                                             -4,  // dx
@@ -119,39 +118,38 @@
   HardwareState hardware_state = make_hwstate(200000, 0, 1, 1, &finger_state);
 
   stime_t timeout = NO_DEADLINE;
-  base_interpreter->expected_hwstate_ = &hardware_state;
+  base_interpreter.expected_hwstate_ = &hardware_state;
   Gesture* result = wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_TRUE(base_interpreter->return_value_ == *result);
+  EXPECT_TRUE(base_interpreter.return_value_ == *result);
   ASSERT_GT(timeout, 0);
   stime_t now = hardware_state.timestamp + timeout;
   timeout = NO_DEADLINE;
   result = wrapper.HandleTimer(now, &timeout);
-  EXPECT_TRUE(base_interpreter->return_value_ == *result);
+  EXPECT_TRUE(base_interpreter.return_value_ == *result);
   ASSERT_LT(timeout, 0);
-  EXPECT_EQ(1, base_interpreter->interpret_call_count_);
-  EXPECT_EQ(1, base_interpreter->handle_timer_call_count_);
+  EXPECT_EQ(1, base_interpreter.interpret_call_count_);
+  EXPECT_EQ(1, base_interpreter.handle_timer_call_count_);
 
   // Now, get the log
-  string initial_log = base_interpreter->Encode();
+  string initial_log = base_interpreter.Encode();
   // Make a new interpreter and push the log through it
   PropRegistry prop_reg2;
-  InterpreterTestInterpreter* base_interpreter2 =
-      new InterpreterTestInterpreter(&prop_reg2);
-  base_interpreter2->SetEventLoggingEnabled(true);
-  base_interpreter2->return_value_ = base_interpreter->return_value_;
-  base_interpreter2->expected_interpreter_name_ = interpreter_name;
+  InterpreterTestInterpreter base_interpreter2(&prop_reg2);
+  base_interpreter2.SetEventLoggingEnabled(true);
+  base_interpreter2.return_value_ = base_interpreter.return_value_;
+  base_interpreter2.expected_interpreter_name_ = interpreter_name;
   MetricsProperties mprops2(&prop_reg2);
 
   ActivityReplay replay(&prop_reg2);
   replay.Parse(initial_log);
 
-  base_interpreter2->expected_hwstate_ = &hardware_state;
+  base_interpreter2.expected_hwstate_ = &hardware_state;
 
-  replay.Replay(base_interpreter2, &mprops2);
-  string final_log = base_interpreter2->Encode();
+  replay.Replay(&base_interpreter2, &mprops2);
+  string final_log = base_interpreter2.Encode();
   EXPECT_EQ(initial_log, final_log);
-  EXPECT_EQ(1, base_interpreter2->interpret_call_count_);
-  EXPECT_EQ(1, base_interpreter2->handle_timer_call_count_);
+  EXPECT_EQ(1, base_interpreter2.interpret_call_count_);
+  EXPECT_EQ(1, base_interpreter2.handle_timer_call_count_);
 }
 
 class InterpreterResetLogTestInterpreter : public Interpreter {
@@ -168,10 +166,9 @@
 
 TEST(InterpreterTest, ResetLogTest) {
   PropRegistry prop_reg;
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
-  base_interpreter->SetEventLoggingEnabled(true);
-  TestInterpreterWrapper wrapper(base_interpreter);
+  InterpreterResetLogTestInterpreter base_interpreter;
+  base_interpreter.SetEventLoggingEnabled(true);
+  TestInterpreterWrapper wrapper(&base_interpreter);
 
   FingerState finger_state = {
     // TM, Tm, WM, Wm, Press, Orientation, X, Y, TrID
@@ -180,24 +177,23 @@
   HardwareState hardware_state = make_hwstate(200000, 0, 1, 1, &finger_state);
   stime_t timeout = NO_DEADLINE;
   wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 1);
+  EXPECT_EQ(base_interpreter.log_->size(), 1);
 
   wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 2);
+  EXPECT_EQ(base_interpreter.log_->size(), 2);
 
   // Assume the ResetLog property is set.
-  base_interpreter->Clear();
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  base_interpreter.Clear();
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 
   wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 1);
+  EXPECT_EQ(base_interpreter.log_->size(), 1);
 }
 
 TEST(InterpreterTest, LoggingDisabledByDefault) {
   PropRegistry prop_reg;
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
-  TestInterpreterWrapper wrapper(base_interpreter);
+  InterpreterResetLogTestInterpreter base_interpreter;
+  TestInterpreterWrapper wrapper(&base_interpreter);
 
   FingerState finger_state = {
     // TM, Tm, WM, Wm, Press, Orientation, X, Y, TrID
@@ -206,102 +202,98 @@
   HardwareState hardware_state = make_hwstate(200000, 0, 1, 1, &finger_state);
   stime_t timeout = NO_DEADLINE;
   wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 
   wrapper.SyncInterpret(hardware_state, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 }
 
 TEST(InterpreterTest, EventDebugLoggingEnableTest) {
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
+  InterpreterResetLogTestInterpreter base_interpreter;
 
-  base_interpreter->SetEventDebugLoggingEnabled(0);
-  EXPECT_EQ(base_interpreter->GetEventDebugLoggingEnabled(), 0);
+  base_interpreter.SetEventDebugLoggingEnabled(0);
+  EXPECT_EQ(base_interpreter.GetEventDebugLoggingEnabled(), 0);
 
   using EventDebug = ActivityLog::EventDebug;
-  base_interpreter->EventDebugLoggingEnable(EventDebug::HardwareState);
-  EXPECT_EQ(base_interpreter->GetEventDebugLoggingEnabled(),
+  base_interpreter.EventDebugLoggingEnable(EventDebug::HardwareState);
+  EXPECT_EQ(base_interpreter.GetEventDebugLoggingEnabled(),
             1 << static_cast<int>(EventDebug::HardwareState));
 
-  base_interpreter->EventDebugLoggingDisable(EventDebug::HardwareState);
-  EXPECT_EQ(base_interpreter->GetEventDebugLoggingEnabled(), 0);
+  base_interpreter.EventDebugLoggingDisable(EventDebug::HardwareState);
+  EXPECT_EQ(base_interpreter.GetEventDebugLoggingEnabled(), 0);
 }
 
 TEST(InterpreterTest, LogHardwareStateTest) {
   PropRegistry prop_reg;
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
+  InterpreterResetLogTestInterpreter base_interpreter;
 
   FingerState fs = { 0.0, 0.0, 0.0, 0.0, 9.0, 0.0, 3.0, 4.0, 22, 0 };
   HardwareState hs = make_hwstate(1.0, 0, 1, 1, &fs);
 
-  base_interpreter->SetEventLoggingEnabled(false);
-  base_interpreter->SetEventDebugLoggingEnabled(0);
+  base_interpreter.SetEventLoggingEnabled(false);
+  base_interpreter.SetEventDebugLoggingEnabled(0);
 
-  base_interpreter->LogHardwareStatePre(
+  base_interpreter.LogHardwareStatePre(
       "InterpreterTest_LogHardwareStateTest", hs);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 
-  base_interpreter->LogHardwareStatePost(
+  base_interpreter.LogHardwareStatePost(
       "InterpreterTest_LogHardwareStateTest", hs);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 
   using EventDebug = ActivityLog::EventDebug;
-  base_interpreter->SetEventLoggingEnabled(true);
-  base_interpreter->EventDebugLoggingEnable(EventDebug::HardwareState);
+  base_interpreter.SetEventLoggingEnabled(true);
+  base_interpreter.EventDebugLoggingEnable(EventDebug::HardwareState);
 
-  base_interpreter->LogHardwareStatePre(
+  base_interpreter.LogHardwareStatePre(
       "InterpreterTest_LogHardwareStateTest", hs);
-  EXPECT_EQ(base_interpreter->log_->size(), 1);
+  EXPECT_EQ(base_interpreter.log_->size(), 1);
 
-  base_interpreter->LogHardwareStatePost(
+  base_interpreter.LogHardwareStatePost(
       "InterpreterTest_LogHardwareStateTest", hs);
-  EXPECT_EQ(base_interpreter->log_->size(), 2);
+  EXPECT_EQ(base_interpreter.log_->size(), 2);
 }
 
 TEST(InterpreterTest, LogGestureTest) {
   PropRegistry prop_reg;
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
+  InterpreterResetLogTestInterpreter base_interpreter;
 
   Gesture move(kGestureMove, 1.0, 2.0, 773, 4.0);
 
-  base_interpreter->SetEventLoggingEnabled(false);
-  base_interpreter->SetEventDebugLoggingEnabled(0);
-  base_interpreter->LogGestureConsume("InterpreterTest_LogGestureTest", move);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
-  base_interpreter->LogGestureProduce("InterpreterTest_LogGestureTest", move);
-  EXPECT_EQ(base_interpreter->log_->size(), 0);
+  base_interpreter.SetEventLoggingEnabled(false);
+  base_interpreter.SetEventDebugLoggingEnabled(0);
+  base_interpreter.LogGestureConsume("InterpreterTest_LogGestureTest", move);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
+  base_interpreter.LogGestureProduce("InterpreterTest_LogGestureTest", move);
+  EXPECT_EQ(base_interpreter.log_->size(), 0);
 
 
   using EventDebug = ActivityLog::EventDebug;
-  base_interpreter->SetEventLoggingEnabled(true);
-  base_interpreter->EventDebugLoggingEnable(EventDebug::Gesture);
-  base_interpreter->LogGestureConsume("InterpreterTest_LogGestureTest", move);
-  EXPECT_EQ(base_interpreter->log_->size(), 1);
-  base_interpreter->LogGestureProduce("InterpreterTest_LogGestureTest", move);
-  EXPECT_EQ(base_interpreter->log_->size(), 2);
+  base_interpreter.SetEventLoggingEnabled(true);
+  base_interpreter.EventDebugLoggingEnable(EventDebug::Gesture);
+  base_interpreter.LogGestureConsume("InterpreterTest_LogGestureTest", move);
+  EXPECT_EQ(base_interpreter.log_->size(), 1);
+  base_interpreter.LogGestureProduce("InterpreterTest_LogGestureTest", move);
+  EXPECT_EQ(base_interpreter.log_->size(), 2);
 }
 
 TEST(InterpreterTest, LogHandleTimerTest) {
   PropRegistry prop_reg;
-  InterpreterResetLogTestInterpreter* base_interpreter =
-      new InterpreterResetLogTestInterpreter();
+  InterpreterResetLogTestInterpreter base_interpreter;
 
   using EventDebug = ActivityLog::EventDebug;
-  base_interpreter->SetEventLoggingEnabled(true);
-  base_interpreter->EventDebugLoggingEnable(EventDebug::HandleTimer);
+  base_interpreter.SetEventLoggingEnabled(true);
+  base_interpreter.EventDebugLoggingEnable(EventDebug::HandleTimer);
 
   stime_t timeout = 10;
 
-  base_interpreter->LogHandleTimerPre("InterpreterTest_LogHandleTimerTest",
+  base_interpreter.LogHandleTimerPre("InterpreterTest_LogHandleTimerTest",
         0, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 1);
+  EXPECT_EQ(base_interpreter.log_->size(), 1);
 
-  base_interpreter->LogHandleTimerPost("InterpreterTest_LogHandleTimerTest",
+  base_interpreter.LogHandleTimerPost("InterpreterTest_LogHandleTimerTest",
         0, &timeout);
-  EXPECT_EQ(base_interpreter->log_->size(), 2);
+  EXPECT_EQ(base_interpreter.log_->size(), 2);
 }
 
 }  // namespace gestures
diff --git a/src/lookahead_filter_interpreter.cc b/src/lookahead_filter_interpreter.cc
index 7a5c8fd..767f350 100644
--- a/src/lookahead_filter_interpreter.cc
+++ b/src/lookahead_filter_interpreter.cc
@@ -5,6 +5,7 @@
 #include "include/lookahead_filter_interpreter.h"
 
 #include <algorithm>
+#include <memory>
 #include <math.h>
 
 #include "include/tracer.h"
@@ -472,7 +473,8 @@
       // SyncInterpret
       last_interpreted_time_ = node->state_.timestamp;
       const size_t finger_cnt = node->state_.finger_cnt;
-      FingerState fs_copy[std::max(finger_cnt,(size_t)1)];
+      auto fs_copy =
+        std::make_unique<FingerState[]>(std::max(finger_cnt, (size_t)1));
       std::copy(&node->state_.fingers[0],
                 &node->state_.fingers[finger_cnt],
                 &fs_copy[0]);
@@ -481,7 +483,7 @@
         node->state_.buttons_down,
         node->state_.finger_cnt,
         node->state_.touch_cnt,
-        fs_copy,
+        fs_copy.get(),
         node->state_.rel_x,
         node->state_.rel_y,
         node->state_.rel_wheel,
diff --git a/src/mouse_interpreter.cc b/src/mouse_interpreter.cc
index 6ed44e7..a281a9a 100644
--- a/src/mouse_interpreter.cc
+++ b/src/mouse_interpreter.cc
@@ -31,6 +31,7 @@
       scroll_sensitivity_(prop_reg,"Mouse Scroll Sensitivity",
         kMouseScrollSensitivityDefaultValue),
       hi_res_scrolling_(prop_reg, "Mouse High Resolution Scrolling", true),
+      scroll_velocity_buffer_size_(prop_reg, "Scroll Wheel Velocity Buffer", 3),
       scroll_accel_curve_prop_(prop_reg, "Mouse Scroll Accel Curve",
           scroll_accel_curve_, sizeof(scroll_accel_curve_) / sizeof(double)),
       scroll_max_allowed_input_speed_(prop_reg,
@@ -49,8 +50,6 @@
                                    "Output Mouse Wheel Gestures", false) {
   InitName();
   memset(&prev_state_, 0, sizeof(prev_state_));
-  memset(&last_wheel_, 0, sizeof(last_wheel_));
-  memset(&last_hwheel_, 0, sizeof(last_hwheel_));
   // Scroll acceleration curve coefficients. See the definition for more
   // details on how to generate them.
   scroll_accel_curve_[0] = 1.0374e+01;
@@ -168,60 +167,81 @@
                                                  bool is_vertical) {
   const char name[] = "MouseInterpreter::InterpretScrollWheelEvent";
 
-  const float scroll_wheel_event_time_delta_min = 0.008;
+  const size_t max_buffer_size = scroll_velocity_buffer_size_.val_;
+  const float scroll_wheel_event_time_delta_min = 0.008 * max_buffer_size;
   bool use_high_resolution =
       is_vertical && hwprops_->wheel_is_hi_res
       && hi_res_scrolling_.val_;
   // Vertical wheel or horizontal wheel.
-  float current_wheel_value = hwstate.rel_hwheel;
-  int ticks = hwstate.rel_hwheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
-  WheelRecord* last_wheel_record = &last_hwheel_;
+  WheelRecord current_wheel;
+  current_wheel.timestamp = hwstate.timestamp;
+  int ticks;
+  std::vector<WheelRecord>* last_wheels;
   if (is_vertical) {
     // Only vertical high-res scrolling is supported for now.
     if (use_high_resolution) {
-      current_wheel_value = hwstate.rel_wheel_hi_res
+      current_wheel.change = hwstate.rel_wheel_hi_res
           / REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
       ticks = hwstate.rel_wheel_hi_res;
     } else {
-      current_wheel_value = hwstate.rel_wheel;
+      current_wheel.change = hwstate.rel_wheel;
       ticks = hwstate.rel_wheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
     }
-    last_wheel_record = &last_wheel_;
+    last_wheels = &last_vertical_wheels_;
+  } else {
+    last_wheels = &last_horizontal_wheels_;
+    current_wheel.change = hwstate.rel_hwheel;
+    ticks = hwstate.rel_hwheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
   }
 
   // Check if the wheel is scrolled.
-  if (current_wheel_value) {
+  if (current_wheel.change) {
     stime_t start_time, end_time = hwstate.timestamp;
     // Check if this scroll is in same direction as previous scroll event.
-    if ((current_wheel_value < 0 && last_wheel_record->value < 0) ||
-        (current_wheel_value > 0 && last_wheel_record->value > 0)) {
-      start_time = last_wheel_record->timestamp;
+    if (!last_wheels->empty() &&
+        ((current_wheel.change < 0 && last_wheels->back().change < 0) ||
+         (current_wheel.change > 0 && last_wheels->back().change > 0))) {
+      start_time = last_wheels->begin()->timestamp;
     } else {
+      last_wheels->clear();
       start_time = end_time;
     }
 
-    // If start_time == end_time, compute velocity using dt = 1 second.
-    // (this happens when the user initially starts scrolling)
-    stime_t dt = (end_time - start_time) ?: 1.0;
-    if (dt < scroll_wheel_event_time_delta_min) {
-      // the first packet received after BT wakeup may be delayed, causing the
-      // time delta between that and the subsequent packet to be very small.
-      // Prevent small time deltas from triggering large amounts of acceleration
-      // by enforcing a minimum time delta.
-      dt = scroll_wheel_event_time_delta_min;
+    // We will only accelerate scrolls if we have filled our buffer of scroll
+    // events all in the same direction. If the buffer is full, then calculate
+    // scroll velocity using the average velocity of the entire buffer.
+    float velocity;
+    if (last_wheels->size() < max_buffer_size) {
+      velocity = 0.0;
+    } else {
+      stime_t dt = end_time - last_wheels->back().timestamp;
+      if (dt < scroll_wheel_event_time_delta_min) {
+        // The first packets received after BT wakeup may be delayed, causing
+        // the time delta between that and the subsequent packets to be
+        // artificially very small.
+        // Prevent small time deltas from triggering large amounts of
+        // acceleration by enforcing a minimum time delta.
+        dt = scroll_wheel_event_time_delta_min;
+      }
+
+      last_wheels->pop_back();
+      float buffer_scroll_distance = current_wheel.change;
+      for (auto wheel : *last_wheels) {
+        buffer_scroll_distance += wheel.change;
+      }
+
+      velocity = buffer_scroll_distance / dt;
     }
+    last_wheels->insert(last_wheels->begin(), current_wheel);
 
     // When scroll acceleration is off, the scroll factor does not relate to
     // scroll velocity. It's simply a constant multiplier to the wheel value.
     const double unaccel_scroll_factors[] = { 20.0, 36.0, 72.0, 112.0, 164.0 };
 
-    float velocity = current_wheel_value / dt;
-    float offset = current_wheel_value * (
+    float offset = current_wheel.change * (
       scroll_acceleration_.val_?
       ComputeScrollAccelFactor(velocity) :
       unaccel_scroll_factors[scroll_sensitivity_.val_ - 1]);
-    last_wheel_record->timestamp = hwstate.timestamp;
-    last_wheel_record->value = current_wheel_value;
 
     if (is_vertical) {
       // For historical reasons the vertical wheel (REL_WHEEL) is inverted
diff --git a/src/mouse_interpreter_unittest.cc b/src/mouse_interpreter_unittest.cc
index 57267d8..520a455 100644
--- a/src/mouse_interpreter_unittest.cc
+++ b/src/mouse_interpreter_unittest.cc
@@ -103,6 +103,7 @@
 
   mi.output_mouse_wheel_gestures_.val_ = true;
   mi.hi_res_scrolling_.val_ = 1;
+  mi.scroll_velocity_buffer_size_.val_ = 1;
 
   gs = wrapper.SyncInterpret(hwstates[0], nullptr);
   EXPECT_EQ(nullptr, gs);
@@ -157,6 +158,7 @@
   mi.scroll_acceleration_.val_ = true;
   mi.output_mouse_wheel_gestures_.val_ = true;
   mi.hi_res_scrolling_.val_ = false;
+  mi.scroll_velocity_buffer_size_.val_ = 1;
 
   gs = wrapper.SyncInterpret(hwstates[0], nullptr);
   EXPECT_EQ(nullptr, gs);
@@ -212,6 +214,7 @@
   };
 
   mi.output_mouse_wheel_gestures_.val_ = true;
+  mi.scroll_velocity_buffer_size_.val_ = 1;
 
   gs = wrapper.SyncInterpret(hwstates[0], nullptr);
   ASSERT_NE(nullptr, gs);
diff --git a/src/non_linearity_filter_interpreter.cc b/src/non_linearity_filter_interpreter.cc
index df0e765..4d5c1e4 100644
--- a/src/non_linearity_filter_interpreter.cc
+++ b/src/non_linearity_filter_interpreter.cc
@@ -4,6 +4,8 @@
 
 #include "include/non_linearity_filter_interpreter.h"
 
+#include <cstring>
+
 #include <linux/in.h>
 
 namespace {
@@ -20,7 +22,7 @@
     : FilterInterpreter(nullptr, next, tracer, false),
       x_range_len_(0), y_range_len_(0), p_range_len_(0),
       enabled_(prop_reg, "Enable non-linearity correction", false),
-      data_location_(prop_reg, "Non-linearity correction data file", "None") {
+      data_location_(prop_reg, "Non-linearity correction data file", "") {
   InitName();
   LoadData();
 }
@@ -67,8 +69,12 @@
 }
 
 void NonLinearityFilterInterpreter::LoadData() {
+  if (strlen(data_location_.val_) == 0) {
+    return;
+  }
   FILE* data_fd = fopen(data_location_.val_, "rb");
   if (!data_fd) {
+    // TODO(b/329268257): make this an Err, not a Log.
     Log("Unable to open non-linearity filter data '%s'", data_location_.val_);
     return;
   }
diff --git a/src/prop_registry.cc b/src/prop_registry.cc
index 283db9d..d1aaf6e 100644
--- a/src/prop_registry.cc
+++ b/src/prop_registry.cc
@@ -106,22 +106,24 @@
 }
 
 void BoolArrayProperty::CreatePropImpl() {
-  GesturesPropBool orig_vals[count_];
-  memcpy(orig_vals, vals_, sizeof(orig_vals));
+  auto orig_vals = std::make_unique<GesturesPropBool[]>(count_);
+
+  memcpy(orig_vals.get(), vals_, count_ * sizeof(GesturesPropBool));
   gprop_ = parent_->PropProvider()->create_bool_fn(
       parent_->PropProviderData(),
       name(),
       vals_,
       count_,
       vals_);
-  if (delegate_ && memcmp(orig_vals, vals_, sizeof(orig_vals)))
+  if (delegate_ && memcmp(orig_vals.get(), vals_,
+                          count_ * sizeof(GesturesPropBool)))
     delegate_->BoolArrayWasWritten(this);
 }
 
 Json::Value BoolArrayProperty::NewValue() const {
   Json::Value list(Json::arrayValue);
   for (size_t i = 0; i < count_; i++)
-    list.append(new Json::Value(vals_[i] != 0));
+    list.append(Json::Value(vals_[i] != 0));
   return list;
 }
 
@@ -182,15 +184,16 @@
 }
 
 void DoubleArrayProperty::CreatePropImpl() {
-  float orig_vals[count_];
-  memcpy(orig_vals, vals_, sizeof(orig_vals));
+  auto orig_vals = std::make_unique<float[]>(count_);
+
+  memcpy(orig_vals.get(), vals_, count_ * sizeof(float));
   gprop_ = parent_->PropProvider()->create_real_fn(
       parent_->PropProviderData(),
       name(),
       vals_,
       count_,
       vals_);
-  if (delegate_ && memcmp(orig_vals, vals_, sizeof(orig_vals)))
+  if (delegate_ && memcmp(orig_vals.get(), vals_, count_ * sizeof(float)))
     delegate_->DoubleArrayWasWritten(this);
 }
 
@@ -263,15 +266,16 @@
 }
 
 void IntArrayProperty::CreatePropImpl() {
-  int orig_vals[count_];
-  memcpy(orig_vals, vals_, sizeof(orig_vals));
+  auto orig_vals = std::make_unique<int[]>(count_);
+
+  memcpy(orig_vals.get(), vals_, count_ * sizeof(int));
   gprop_ = parent_->PropProvider()->create_int_fn(
       parent_->PropProviderData(),
       name(),
       vals_,
       count_,
       vals_);
-  if (delegate_ && memcmp(orig_vals, vals_, sizeof(orig_vals)))
+  if (delegate_ && memcmp(orig_vals.get(), vals_, count_ * sizeof(int)))
     delegate_->IntArrayWasWritten(this);
 }
 
diff --git a/src/string_util.cc b/src/string_util.cc
index 3e5b896..863399c 100644
--- a/src/string_util.cc
+++ b/src/string_util.cc
@@ -85,58 +85,24 @@
   }
 }
 
-template <typename STR>
-void SplitStringT(const STR& str,
-                  const typename STR::value_type s,
-                  bool trim_whitespace,
-                  std::vector<STR>* r) {
-  r->clear();
-  size_t last = 0;
-  size_t c = str.size();
-  for (size_t i = 0; i <= c; ++i) {
-    if (i == c || str[i] == s) {
-      STR tmp(str, last, i - last);
-      if (trim_whitespace)
-        TrimWhitespaceASCII(tmp, TRIM_ALL, &tmp);
-      // Avoid converting an empty or all-whitespace source string into a vector
-      // of one empty string.
-      if (i != c || !r->empty() || !tmp.empty())
-        r->push_back(tmp);
-      last = i + 1;
-    }
-  }
-}
-
 template<typename STR>
-TrimPositions TrimStringT(const STR& input,
-                          const typename STR::value_type trim_chars[],
-                          TrimPositions positions,
-                          STR* output) {
-  // Find the edges of leading/trailing whitespace as desired.
-  const typename STR::size_type last_char = input.length() - 1;
-  const typename STR::size_type first_good_char = (positions & TRIM_LEADING) ?
-      input.find_first_not_of(trim_chars) : 0;
-  const typename STR::size_type last_good_char = (positions & TRIM_TRAILING) ?
-      input.find_last_not_of(trim_chars) : last_char;
+STR TrimStringT(const STR& input, const typename STR::value_type trim_chars[]) {
+  if (input.empty()) {
+    return "";
+  }
 
-  // When the string was all whitespace, report that we stripped off whitespace
-  // from whichever position the caller was interested in.  For empty input, we
-  // stripped no whitespace, but we still need to clear |output|.
-  if (input.empty() ||
-      (first_good_char == STR::npos) || (last_good_char == STR::npos)) {
-    bool input_was_empty = input.empty();  // in case output == &input
-    output->clear();
-    return input_was_empty ? TRIM_NONE : positions;
+  // Find the edges of leading/trailing whitespace.
+  const typename STR::size_type first_good_char =
+      input.find_first_not_of(trim_chars);
+  const typename STR::size_type last_good_char =
+      input.find_last_not_of(trim_chars);
+
+  if (first_good_char == STR::npos || last_good_char == STR::npos) {
+    return "";
   }
 
   // Trim the whitespace.
-  *output =
-      input.substr(first_good_char, last_good_char - first_good_char + 1);
-
-  // Return where we trimmed from.
-  return static_cast<TrimPositions>(
-      ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) |
-      ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING));
+  return input.substr(first_good_char, last_good_char - first_good_char + 1);
 }
 
 }  // namespace
@@ -154,25 +120,8 @@
   return result;
 }
 
-bool StartsWithASCII(const std::string& str,
-                     const std::string& search,
-                     bool case_sensitive) {
-  if (case_sensitive)
-    return str.compare(0, search.length(), search) == 0;
-  else
-    return strncasecmp(str.c_str(), search.c_str(), search.length()) == 0;
-}
-
-void SplitString(const std::string& str,
-                 char c,
-                 std::vector<std::string>* r) {
-  SplitStringT(str, c, true, r);
-}
-
-TrimPositions TrimWhitespaceASCII(const std::string& input,
-                                  TrimPositions positions,
-                                  std::string* output) {
-  return TrimStringT(input, kWhitespaceASCII, positions, output);
+std::string TrimWhitespaceASCII(const std::string& input) {
+  return TrimStringT(input, kWhitespaceASCII);
 }
 
 }  // namespace gestures
diff --git a/src/string_util_unittest.cc b/src/string_util_unittest.cc
index 0ce0b0c..b1edda6 100644
--- a/src/string_util_unittest.cc
+++ b/src/string_util_unittest.cc
@@ -13,9 +13,7 @@
 
 class StringUtilTest : public ::testing::Test {};
 
-// This test adds code coverage to string_util.
-
-TEST(StringUtilTest, SimpleTest) {
+TEST(StringUtilTest, StringPrintfTest) {
   const char *pstr =
     "0123456789012345678901234567890123456789012345678901234567890123456789";
   std::string str = StringPrintf(
@@ -26,17 +24,16 @@
   );
   int expected_length = (70*15)+15+1;
   EXPECT_EQ(str.size(), expected_length);
+}
 
-  TrimPositions trimmed_from = TrimWhitespaceASCII(str, TRIM_ALL, &str);
-  EXPECT_EQ(trimmed_from, TRIM_ALL);
-  EXPECT_EQ(str.size(), expected_length-2);
-
-  std::vector<std::string> split;
-  SplitString(str, ' ', &split);
-  EXPECT_EQ(split.size(), 15);
-
-  bool matches = StartsWithASCII(split[0], pstr, true);
-  EXPECT_TRUE(matches);
+TEST(StringUtilTest, TrimWhitespaceASCIITest) {
+  EXPECT_EQ(TrimWhitespaceASCII(""), "");
+  EXPECT_EQ(TrimWhitespaceASCII(" x    "), "x");
+  EXPECT_EQ(TrimWhitespaceASCII("badger"), "badger");
+  EXPECT_EQ(TrimWhitespaceASCII("badger  "), "badger");
+  EXPECT_EQ(TrimWhitespaceASCII("  badger"), "badger");
+  EXPECT_EQ(TrimWhitespaceASCII("  \t \n\r "), "");
+  EXPECT_EQ(TrimWhitespaceASCII("   Bees and ponies     "), "Bees and ponies");
 }
 
 }  // namespace gestures
diff --git a/tools/touchtests-report.json b/tools/touchtests-report.json
index 8340e7b..145f6aa 100644
--- a/tools/touchtests-report.json
+++ b/tools/touchtests-report.json
@@ -1798,6 +1798,13 @@
     "result": "success",
     "score": 1.0
   },
+  "logitech-m650-1.0/fast_initial_scroll": {
+    "description": "",
+    "disabled": false,
+    "error": "",
+    "result": "success",
+    "score": 1.0
+  },
   "logitech-t620/accidental_back_fling": {
     "description": "",
     "disabled": false,