Snap for 9100386 from 9b40752880ad12a6afd219e0c0f97d9933f1a174 to tm-d4-release

Change-Id: I3aae66c0722fe7f074f13bf3f2ea925caaf54ab4
diff --git a/Android.bp b/Android.bp
index 7211fd9..d0ab4dd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,6 +18,7 @@
     local_include_dirs: ["."],
     export_include_dirs: ["."],
     srcs: [
+        "chrome_to_android_compatibility.cc",
         "ui/events/ozone/features.cc",
         "ui/events/ozone/evdev/touch_evdev_types.cc",
         "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc",
diff --git a/chrome_to_android_compatibility.cc b/chrome_to_android_compatibility.cc
new file mode 100644
index 0000000..18b5a80
--- /dev/null
+++ b/chrome_to_android_compatibility.cc
@@ -0,0 +1,15 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome_to_android_compatibility.h"
+
+// Android's external/libchrome directory is out of date.
+// Add missing templates here as a temporary solution
+namespace base {
+
+bool operator==(const TimeTicks& t1, const TimeTicks& t2) {
+  return t1.since_origin() == t2.since_origin();
+}
+
+}  // namespace base
diff --git a/chrome_to_android_compatibility.h b/chrome_to_android_compatibility.h
index f51ea36..91c442a 100644
--- a/chrome_to_android_compatibility.h
+++ b/chrome_to_android_compatibility.h
@@ -10,6 +10,14 @@
 // Add missing templates here as a temporary solution
 namespace base {
 
+/**
+ * Workaround for the error in unit tests: ISO C++20 considers use of overloaded
+ * operator '==' (with operand types 'const base::TimeTicks'
+ * and 'const base::TimeTicks') to be ambiguous despite there being a unique
+ * best viable function [-Werror,-Wambiguous-reversed-operator]
+ */
+bool operator==(const TimeTicks& t1, const TimeTicks& t2);
+
 namespace time_internal {
 
 // clang-format off
diff --git a/chrome_to_android_compatibility_test_support.cc b/chrome_to_android_compatibility_test_support.cc
index 5aa94b9..d1fe1e5 100644
--- a/chrome_to_android_compatibility_test_support.cc
+++ b/chrome_to_android_compatibility_test_support.cc
@@ -6,12 +6,6 @@
 
 #include "base/time/time.h"
 
-namespace base {
-bool operator==(const TimeTicks& t1, const TimeTicks& t2) {
-  return t1.since_origin() == t2.since_origin();
-}
-}  // namespace base
-
 namespace gfx {
 
 // clang-format off
diff --git a/chrome_to_android_compatibility_test_support.h b/chrome_to_android_compatibility_test_support.h
deleted file mode 100644
index c2e4c90..0000000
--- a/chrome_to_android_compatibility_test_support.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#pragma once
-
-#include "base/time/time.h"
-
-namespace base {
-
-/**
- * Workaround for the error in unit tests: ISO C++20 considers use of overloaded
- * operator '==' (with operand types 'const base::TimeTicks'
- * and 'const base::TimeTicks') to be ambiguous despite there being a unique
- * best viable function [-Werror,-Wambiguous-reversed-operator]
- */
-bool operator==(const TimeTicks& t1, const TimeTicks& t2);
-
-}  // namespace base
diff --git a/copy.bara.sky b/copy.bara.sky
index c270d38..cb88b0c 100644
--- a/copy.bara.sky
+++ b/copy.bara.sky
@@ -90,9 +90,9 @@
             "OWNERS",
             "PREUPLOAD.cfg",
             "TEST_MAPPING",
+            "chrome_to_android_compatibility.cc",
             "chrome_to_android_compatibility.h",
             "chrome_to_android_compatibility_test_support.cc",
-            "chrome_to_android_compatibility_test_support.h",
             "copy.bara.sky",
         ],
     ),
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
index 0b301e4..9a8e385 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc
@@ -37,20 +37,49 @@
 bool IsEarlyStageSample(
     const PalmFilterStroke& stroke,
     const NeuralStylusPalmDetectionFilterModelConfig& config) {
-  return config.early_stage_sample_counts.find(stroke.samples_seen()) !=
-         config.early_stage_sample_counts.end();
+  if (!config.resample_period) {
+    return config.early_stage_sample_counts.find(stroke.samples_seen()) !=
+           config.early_stage_sample_counts.end();
+  }
+  // Duration is not well-defined for sample_count <= 1, so we handle
+  // it separately.
+  if (stroke.samples().empty()) {
+    return false;
+  }
+  if (stroke.samples().size() == 1) {
+    return config.early_stage_sample_counts.find(1) !=
+           config.early_stage_sample_counts.end();
+  }
+  for (const uint32_t sample_count : config.early_stage_sample_counts) {
+    const base::TimeDelta duration = config.GetEquivalentDuration(sample_count);
+    // Previous sample must not have passed the 'duration' threshold, but the
+    // current sample must pass the threshold
+    if (stroke.LastSampleCrossed(duration)) {
+      return true;
+    }
+  }
+  return false;
 }
 
 bool HasDecidedStroke(
     const PalmFilterStroke& stroke,
     const NeuralStylusPalmDetectionFilterModelConfig& config) {
-  return stroke.samples_seen() >= config.max_sample_count;
+  if (!config.resample_period) {
+    return stroke.samples_seen() >= config.max_sample_count;
+  }
+  const base::TimeDelta max_duration =
+      config.GetEquivalentDuration(config.max_sample_count);
+  return stroke.Duration() >= max_duration;
 }
 
 bool IsVeryShortStroke(
     const PalmFilterStroke& stroke,
     const NeuralStylusPalmDetectionFilterModelConfig& config) {
-  return stroke.samples_seen() < config.min_sample_count;
+  if (!config.resample_period) {
+    return stroke.samples_seen() < config.min_sample_count;
+  }
+  return stroke.Duration() <
+         config.GetEquivalentDuration(config.min_sample_count);
 }
 
 /**
@@ -58,9 +87,15 @@
  * being evaluated. The parameter 'neighbor_min_sample_count' might be different
  * from the config, depending on the specific usage in the caller.
  */
-bool HasInsufficientDataAsNeighbor(const PalmFilterStroke& neighbor_stroke,
-                                   size_t neighbor_min_sample_count) {
-  return neighbor_stroke.samples().size() < neighbor_min_sample_count;
+bool HasInsufficientDataAsNeighbor(
+    const PalmFilterStroke& neighbor_stroke,
+    size_t neighbor_min_sample_count,
+    const NeuralStylusPalmDetectionFilterModelConfig& config) {
+  if (!config.resample_period) {
+    return neighbor_stroke.samples().size() < neighbor_min_sample_count;
+  }
+  return neighbor_stroke.Duration() <
+         config.GetEquivalentDuration(neighbor_min_sample_count);
 }
 
 }  // namespace
@@ -105,7 +140,8 @@
     if (neighbor.tracking_id() == stroke.tracking_id()) {
       continue;
     }
-    if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) {
+    if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count,
+                                      model_->config())) {
       continue;
     }
     float distance =
@@ -145,7 +181,8 @@
     if (neighbor.tracking_id() == stroke.tracking_id()) {
       continue;
     }
-    if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) {
+    if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count,
+                                      model_->config())) {
       continue;
     }
     float distance =
@@ -300,15 +337,29 @@
     const PalmFilterStroke& stroke) const {
   const NeuralStylusPalmDetectionFilterModelConfig& config = model_->config();
   // Inference only executed once per stroke
-  return stroke.samples_seen() == config.max_sample_count;
+  if (!config.resample_period) {
+    return stroke.samples_seen() == config.max_sample_count;
+  }
+  return stroke.LastSampleCrossed(
+      config.GetEquivalentDuration(config.max_sample_count));
 }
 
 bool NeuralStylusPalmDetectionFilter::IsHeuristicPalmStroke(
     const PalmFilterStroke& stroke) const {
   const auto& config = model_->config();
-  if (stroke.samples().size() >= config.max_sample_count) {
-    LOG(DFATAL) << "Should not call this method on long strokes.";
-    return false;
+  if (config.resample_period) {
+    if (stroke.Duration() >
+        config.GetEquivalentDuration(config.max_sample_count)) {
+      LOG(DFATAL)
+          << "Should not call this method on long strokes. Got duration = "
+          << stroke.Duration();
+      return false;
+    }
+  } else {
+    if (stroke.samples().size() >= config.max_sample_count) {
+      LOG(DFATAL) << "Should not call this method on long strokes.";
+      return false;
+    }
   }
 
   if (config.heuristic_palm_touch_limit > 0.0) {
@@ -401,6 +452,9 @@
 void NeuralStylusPalmDetectionFilter::AppendFeatures(
     const PalmFilterStroke& stroke,
     std::vector<float>* features) const {
+  if (model_->config().resample_period) {
+    return AppendResampledFeatures(stroke, features);
+  }
   const int size = stroke.samples().size();
   for (int i = 0; i < size; ++i) {
     const PalmFilterSample& sample = stroke.samples()[i];
@@ -435,6 +489,59 @@
     features->push_back(samples_seen - model_->config().max_sample_count);
   }
 }
+
+/**
+ * The flow here is similar to 'AppendFeatures' above, but we rely on the
+ * timing of the samples rather than on the explicit number / position of
+ * samples.
+ */
+void NeuralStylusPalmDetectionFilter::AppendResampledFeatures(
+    const PalmFilterStroke& stroke,
+    std::vector<float>* features) const {
+  size_t sample_count = 0;
+  const base::TimeTicks& first_time = stroke.samples()[0].time;
+  const base::TimeDelta& resample_period = *model_->config().resample_period;
+  const base::TimeDelta max_duration =
+      model_->config().GetEquivalentDuration(model_->config().max_sample_count);
+  for (auto time = first_time; (time - first_time) <= max_duration &&
+                               time <= stroke.samples().back().time;
+       time += resample_period) {
+    sample_count++;
+    const PalmFilterSample& sample = stroke.GetSampleAt(time);
+    features->push_back(sample.major_radius);
+    features->push_back(sample.minor_radius <= 0.0 ? sample.major_radius
+                                                   : sample.minor_radius);
+    float distance = 0;
+    if (time != first_time) {
+      distance = EuclideanDistance(
+          stroke.GetSampleAt(time - resample_period).point, sample.point);
+    }
+    features->push_back(distance);
+    features->push_back(sample.edge);
+    features->push_back(1.0);  // existence.
+  }
+  const int padding = model_->config().max_sample_count - sample_count;
+  DCHECK_GE(padding, 0);
+
+  for (int i = 0; i < padding * kFeaturesPerSample; ++i) {
+    features->push_back(0.0);
+  }
+  // "fill proportion."
+  features->push_back(static_cast<float>(sample_count) /
+                      model_->config().max_sample_count);
+  features->push_back(EuclideanDistance(stroke.samples().front().point,
+                                        stroke.samples().back().point));
+
+  // Start sequence number. 0 is min.
+  uint32_t samples_seen =
+      (stroke.Duration() / (*model_->config().resample_period)) + 1;
+  if (samples_seen < model_->config().max_sample_count) {
+    features->push_back(0);
+  } else {
+    features->push_back(samples_seen - model_->config().max_sample_count);
+  }
+}
+
 void NeuralStylusPalmDetectionFilter::AppendFeaturesAsNeighbor(
     const PalmFilterStroke& stroke,
     float distance,
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
index bab332e..c27e9e9 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h
@@ -109,6 +109,8 @@
   std::vector<float> ExtractFeatures(int tracking_id) const;
   void AppendFeatures(const PalmFilterStroke& stroke,
                       std::vector<float>* features) const;
+  void AppendResampledFeatures(const PalmFilterStroke& stroke,
+                               std::vector<float>* features) const;
   void AppendFeaturesAsNeighbor(const PalmFilterStroke& stroke,
                                 float distance,
                                 std::vector<float>* features) const;
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
index da596b8..51aa4df 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc
@@ -4,6 +4,8 @@
 
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h"
 
+#include "base/logging.h"
+
 namespace ui {
 
 NeuralStylusPalmDetectionFilterModelConfig::
@@ -15,4 +17,19 @@
 
 NeuralStylusPalmDetectionFilterModelConfig::
     ~NeuralStylusPalmDetectionFilterModelConfig() = default;
+
+base::TimeDelta
+NeuralStylusPalmDetectionFilterModelConfig::GetEquivalentDuration(
+    uint32_t sample_count) const {
+  if (!resample_period) {
+    LOG(DFATAL) << __func__
+                << " should only be called if resampling is enabled";
+    return base::TimeDelta::FromMicroseconds(0);
+  }
+  if (sample_count <= 1) {
+    return base::TimeDelta::FromMicroseconds(0);
+  }
+  return (sample_count - 1) * (*resample_period);
+}
+
 }  // namespace ui
\ No newline at end of file
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
index dab5455..bcc21b2 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h
@@ -42,6 +42,10 @@
   // Maximum sample count.
   uint32_t max_sample_count = 0;
 
+  // Convert the provided 'sample_count' to an equivalent time duration.
+  // Should only be called when resampling is enabled.
+  base::TimeDelta GetEquivalentDuration(uint32_t sample_count) const;
+
   // Minimum count of samples for a stroke to be considered as a neighbor.
   uint32_t neighbor_min_sample_count = 0;
 
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
index ce250f7..13288f0 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc
@@ -14,7 +14,7 @@
 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
 #include "ui/events/ozone/evdev/event_device_test_util.h"
 #else
-#include "chrome_to_android_compatibility_test_support.h"
+#include "chrome_to_android_compatibility.h"
 #endif
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h"
 #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
@@ -29,7 +29,8 @@
                      const NeuralStylusPalmDetectionFilterModelConfig&());
 };
 
-class NeuralStylusPalmDetectionFilterTest : public testing::Test {
+class NeuralStylusPalmDetectionFilterTest
+    : public testing::TestWithParam<float> {
  public:
   NeuralStylusPalmDetectionFilterTest() = default;
 
@@ -48,6 +49,11 @@
     model_config_.heuristic_palm_touch_limit = 40;
     model_config_.heuristic_palm_area_limit = 1000;
     model_config_.max_dead_neighbor_time = base::Milliseconds(100);
+    const float resample_period = GetParam();
+    if (resample_period != 0.0) {
+      model_config_.resample_period = base::Milliseconds(resample_period);
+      sample_period_ = *model_config_.resample_period;
+    }
     EXPECT_CALL(*model_, config())
         .Times(testing::AnyNumber())
         .WillRepeatedly(testing::ReturnRef(model_config_));
@@ -87,13 +93,19 @@
   MockNeuralModel* model_;
   NeuralStylusPalmDetectionFilterModelConfig model_config_;
   std::unique_ptr<PalmDetectionFilter> palm_detection_filter_;
+  base::TimeDelta sample_period_ = base::Milliseconds(8);
 };
 
-class NeuralStylusPalmDetectionFilterDeathTest
-    : public NeuralStylusPalmDetectionFilterTest {};
+INSTANTIATE_TEST_SUITE_P(ParametricFilterTest,
+                         NeuralStylusPalmDetectionFilterTest,
+                         ::testing::Values(0, 8.0),
+                         [](const auto& paramInfo) {
+                           return paramInfo.param != 0.0 ? "ResamplingEnabled"
+                                                         : "ResamplingDisabled";
+                         });
 
 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
-TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) {
   EventDeviceInfo devinfo;
   std::vector<std::pair<DeviceCapabilities, bool>> devices = {
       {kNocturneTouchScreen, true},
@@ -123,24 +135,26 @@
   }
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) {
+TEST(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) {
   EventDeviceInfo bad_devinfo;
   EXPECT_TRUE(CapabilitiesToDeviceInfo(kNocturneStylus, &bad_devinfo));
+  std::unique_ptr<NeuralStylusPalmDetectionFilterModel> model_(
+      new testing::StrictMock<MockNeuralModel>);
+  std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state =
+      std::make_unique<SharedPalmDetectionFilterState>();
   EXPECT_DCHECK_DEATH({
-    NeuralStylusPalmDetectionFilter f(
-        bad_devinfo,
-        std::unique_ptr<NeuralStylusPalmDetectionFilterModel>(model_),
-        shared_palm_state.get());
+    NeuralStylusPalmDetectionFilter f(bad_devinfo, std::move(model_),
+                                      shared_palm_state.get());
   });
 }
 #endif
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, NameTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, NameTest) {
   EXPECT_EQ("NeuralStylusPalmDetectionFilter",
             palm_detection_filter_->FilterNameForTesting());
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled,
       expected_cancelled;
   touch_[0].touching = true;
@@ -167,7 +181,7 @@
   EXPECT_EQ(expected_cancelled, actual_cancelled);
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   touch_[0].touching = true;
   touch_[0].tracking_id = 600;
@@ -185,7 +199,7 @@
   touch_[0].was_touching = true;
   touch_[0].touching = false;
   touch_[0].tracking_id = -1;
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   EXPECT_TRUE(actual_held.none());
@@ -203,7 +217,7 @@
   touch_[0].was_touching = true;
   touch_[0].touching = false;
   touch_[0].tracking_id = -1;
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   EXPECT_TRUE(actual_held.none());
@@ -212,7 +226,7 @@
   EXPECT_TRUE(actual_cancelled.none());
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTest) {
   // Set up 3 touches as touching:
   // Touch 0 starts off and is sent twice
   // Touch 1 and 2 are then added on: 2 is far away, 1 is nearby. We expect a
@@ -259,7 +273,7 @@
   touch_[2].tracking_id = 502;
   touch_[2].slot = 2;
 
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   EXPECT_TRUE(actual_held.none());
@@ -284,7 +298,7 @@
               Inference(testing::Pointwise(testing::FloatEq(), features)))
       .Times(1)
       .WillOnce(testing::Return(0.5));
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   EXPECT_TRUE(actual_held.none());
@@ -306,7 +320,7 @@
               Inference(testing::Pointwise(testing::FloatEq(), features)))
       .Times(1)
       .WillOnce(testing::Return(0.5));
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   EXPECT_TRUE(actual_held.none());
@@ -314,7 +328,7 @@
   EXPECT_EQ(actual_cancelled, expected_cancelled);
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
 
@@ -369,7 +383,7 @@
               Inference(testing::Pointwise(testing::FloatEq(), features)))
       .Times(1)
       .WillOnce(testing::Return(0.5));
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   // Slot 0 is held.
@@ -425,7 +439,7 @@
       .Times(1)
       .WillOnce(testing::Return(0.5));
 
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
 
@@ -464,7 +478,7 @@
               Inference(testing::Pointwise(testing::FloatEq(), features)))
       .Times(1)
       .WillOnce(testing::Return(0.5));
-  touch_time += base::Milliseconds(8.0f);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
 
@@ -483,7 +497,7 @@
   EXPECT_EQ(actual_cancelled, expected_cancelled);
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   base::TimeTicks touch_time =
       base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0);
@@ -501,7 +515,7 @@
     if (i != 0) {
       touch_[0].was_touching = true;
     }
-    touch_time += base::Milliseconds(8.0f);
+    touch_time += sample_period_;
     palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                    &actual_cancelled);
     ASSERT_TRUE(actual_held.none()) << " Failed at " << i;
@@ -509,7 +523,7 @@
   }
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   std::bitset<kNumTouchEvdevSlots> expected_cancelled;
   base::TimeTicks touch_time =
@@ -533,7 +547,7 @@
     if (i != 0) {
       touch_[0].was_touching = true;
     }
-    touch_time += base::Milliseconds(8.0f);
+    touch_time += sample_period_;
     palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                    &actual_cancelled);
     ASSERT_EQ(original_finger_time,
@@ -553,7 +567,7 @@
   }
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
   model_config_.heuristic_delay_start_if_palm = true;
@@ -573,7 +587,7 @@
   EXPECT_EQ(expected_cancelled, actual_cancelled);
 }
 
-TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
+TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) {
   std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled;
   std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled;
   model_config_.heuristic_delay_start_if_palm = true;
@@ -596,7 +610,7 @@
   // Delay continues even afterwards, until inference time: then it's off.
   for (uint32_t i = 1; i < model_config_.max_sample_count - 1; ++i) {
     touch_[0].was_touching = true;
-    touch_time += base::Milliseconds(10.0);
+    touch_time += sample_period_;
     touch_[0].major = 15;
     touch_[0].minor = 15;
     touch_[0].x += 1;
@@ -610,7 +624,7 @@
   EXPECT_CALL(*model_, Inference(testing::_))
       .Times(1)
       .WillOnce(testing::Return(-0.1));
-  touch_time = base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0);
+  touch_time += sample_period_;
   palm_detection_filter_->Filter(touch_, touch_time, &actual_held,
                                  &actual_cancelled);
   expected_held.set(0, false);
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
index d1813ef..7c581dd 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc
@@ -4,6 +4,7 @@
 
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
 
+#include <base/logging.h>
 #include <algorithm>
 
 namespace ui {
@@ -80,7 +81,7 @@
  * not interpolated, the values are taken from the 'after' sample unless the
  * requested time is very close to the 'before' sample.
  */
-PalmFilterSample getSampleAtTime(base::TimeTicks time,
+PalmFilterSample GetSampleAtTime(base::TimeTicks time,
                                  const PalmFilterSample& before,
                                  const PalmFilterSample& after) {
   // Use the newest sample as the base, except when the requested time is very
@@ -153,16 +154,30 @@
 
 void PalmFilterStroke::ProcessSample(const PalmFilterSample& sample) {
   DCHECK_EQ(tracking_id_, sample.tracking_id);
-  if (resample_period_.has_value()) {
-    Resample(sample);
-    return;
+  if (samples_seen_ == 0) {
+    first_sample_time_ = sample.time;
   }
 
   AddSample(sample);
 
-  while (samples_.size() > max_sample_count_) {
-    AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
-    samples_.pop_front();
+  if (resample_period_.has_value()) {
+    // Prune based on time
+    const base::TimeDelta max_duration =
+        (*resample_period_) * (max_sample_count_ - 1);
+    while (samples_.size() > 2 &&
+           samples_.back().time - samples_[1].time >= max_duration) {
+      // We can only discard the sample if after it's discarded, we still cover
+      // the entire range. If we don't, we need to keep this sample for
+      // calculating resampled values.
+      AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
+      samples_.pop_front();
+    }
+  } else {
+    // Prune based on number of samples
+    while (samples_.size() > max_sample_count_) {
+      AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
+      samples_.pop_front();
+    }
   }
 }
 
@@ -172,36 +187,6 @@
   samples_seen_++;
 }
 
-/**
- * When resampling is enabled, we don't store all samples. Only the resampled
- * values are stored into samples_. In addition, the last real event is stored
- * into last_sample_, which is used to calculate the resampled values.
- */
-void PalmFilterStroke::Resample(const PalmFilterSample& sample) {
-  if (samples_seen_ == 0) {
-    AddSample(sample);
-    last_sample_ = sample;
-    return;
-  }
-
-  // We already have a valid last sample here.
-  DCHECK_LE(last_sample_.time, sample.time);
-  // Generate resampled values
-  base::TimeTicks next_sample_time = samples_.back().time + *resample_period_;
-  while (next_sample_time <= sample.time) {
-    AddSample(getSampleAtTime(next_sample_time, last_sample_, sample));
-    next_sample_time = samples_.back().time + (*resample_period_);
-  }
-  last_sample_ = sample;
-
-  // Prune the resampled collection
-  while ((samples_.back().time - samples_.front().time) >=
-         (*resample_period_) * max_sample_count_) {
-    AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin());
-    samples_.pop_front();
-  }
-}
-
 void PalmFilterStroke::AddToUnscaledCentroid(const gfx::Vector2dF point) {
   const gfx::Vector2dF corrected_point = point - unscaled_centroid_sum_error_;
   const gfx::PointF new_unscaled_centroid =
@@ -226,6 +211,47 @@
   return tracking_id_;
 }
 
+base::TimeDelta PalmFilterStroke::Duration() const {
+  if (samples_.empty()) {
+    LOG(DFATAL) << "No samples available";
+    return base::Milliseconds(0);
+  }
+  return samples_.back().time - first_sample_time_;
+}
+
+base::TimeDelta PalmFilterStroke::PreviousDuration() const {
+  if (samples_.size() <= 1) {
+    LOG(DFATAL) << "Not enough samples";
+    return base::Milliseconds(0);
+  }
+  const PalmFilterSample& secondToLastSample = samples_.rbegin()[1];
+  return secondToLastSample.time - first_sample_time_;
+}
+
+bool PalmFilterStroke::LastSampleCrossed(base::TimeDelta duration) const {
+  if (samples_.size() <= 1) {
+    // If there's only 1 sample, stroke just started and Duration() is zero.
+    return false;
+  }
+  return PreviousDuration() < duration && duration <= Duration();
+}
+
+PalmFilterSample PalmFilterStroke::GetSampleAt(base::TimeTicks time) const {
+  size_t i = 0;
+  for (; i < samples_.size() && samples_[i].time < time; ++i) {
+  }
+
+  if (i < samples_.size() && !samples_.empty() && samples_[i].time == time) {
+    return samples_[i];
+  }
+  if (i == 0 || i == samples_.size()) {
+    LOG(DFATAL) << "Invalid index: " << i
+                << ", can't interpolate for time: " << time;
+    return {};
+  }
+  return GetSampleAtTime(time, samples_[i - 1], samples_[i]);
+}
+
 uint64_t PalmFilterStroke::samples_seen() const {
   return samples_seen_;
 }
@@ -312,11 +338,10 @@
   out << "  max_sample_count_ = " << stroke.max_sample_count_ << "\n";
   if (stroke.resample_period_) {
     out << "  resample_period_ = " << *(stroke.resample_period_) << "\n";
-    out << "  last_sample_ = " << stroke.last_sample_ << "\n";
   } else {
     out << "  resample_period_  = <not set>\n";
-    out << "  last_sample_ = <not valid b/c resampling is off>\n";
   }
+  out << "  first_sample_time_ = " << stroke.first_sample_time_ << "\n";
   out << "  unscaled_centroid_ = " << stroke.unscaled_centroid_ << "\n";
   out << "  unscaled_centroid_sum_error_ = "
       << stroke.unscaled_centroid_sum_error_ << "\n";
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
index 47f0c33..d50a32d 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h
@@ -12,7 +12,7 @@
 #if defined(__ANDROID__) || defined(__ANDROID_HOST__)
 #undef LOG_INFO
 #undef LOG_WARNING
-#include <chrome_to_android_compatibility_test_support.h>
+#include <chrome_to_android_compatibility.h>
 #endif
 #include "base/time/time.h"
 #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
@@ -85,6 +85,25 @@
   float BiggestSize() const;
   // If no elements in stroke, returns 0.0;
   float MaxMajorRadius() const;
+  /**
+   * Return the time duration of this stroke.
+   */
+  base::TimeDelta Duration() const;
+  /**
+   * Provide a (potentially resampled) sample at the requested time.
+   * Only interpolation is allowed.
+   * The requested time must be within the window at which the gesture occurred.
+   */
+  PalmFilterSample GetSampleAt(base::TimeTicks time) const;
+
+  /**
+   * Return true if the provided duration is between the duration of the
+   * previous sample and the current sample. In other words, if the addition of
+   * the last sample caused the total stroke duration to exceed the provided
+   * duration. Return false otherwise.
+   */
+  bool LastSampleCrossed(base::TimeDelta duration) const;
+
   const std::deque<PalmFilterSample>& samples() const;
   uint64_t samples_seen() const;
   int tracking_id() const;
@@ -92,10 +111,8 @@
  private:
   void AddToUnscaledCentroid(const gfx::Vector2dF point);
   void AddSample(const PalmFilterSample& sample);
-  /**
-   * Process the sample. Potentially store the resampled sample into samples_.
-   */
-  void Resample(const PalmFilterSample& sample);
+
+  base::TimeDelta PreviousDuration() const;
 
   std::deque<PalmFilterSample> samples_;
   const int tracking_id_;
@@ -108,13 +125,9 @@
    * number of times 'AddSample' has been called.
    */
   uint64_t samples_seen_ = 0;
-  /**
-   * The last sample seen by the model. Used when resampling is enabled in order
-   * to compute the resampled value.
-   */
-  PalmFilterSample last_sample_;
 
   const uint64_t max_sample_count_;
+  base::TimeTicks first_sample_time_;
   const base::Optional<base::TimeDelta> resample_period_;
 
   gfx::PointF unscaled_centroid_ = gfx::PointF(0., 0.);
diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
index ac3c841..e7019b0 100644
--- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc
@@ -13,7 +13,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #if defined(__ANDROID__) || defined(__ANDROID_HOST__)
 #include <linux/input-event-codes.h>
-#include "chrome_to_android_compatibility_test_support.h"
+#include "chrome_to_android_compatibility.h"
 #else
 #include "ui/events/ozone/evdev/event_device_test_util.h"
 #endif
@@ -393,23 +393,24 @@
   ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time)));
   ASSERT_EQ(1u, stroke.samples_seen());
 
-  // Add second sample at time = T + 2ms. It's not yet time for the new frame,
-  // so no new sample should be generated.
+  // Add second sample at time = T + 4ms. All samples are stored, even if it's
+  // before the next resample time.
   base::TimeTicks time = down_time + base::Milliseconds(4);
   sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
   stroke.ProcessSample(sample);
-  ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time)));
-  ASSERT_EQ(1u, stroke.samples_seen());
+  ASSERT_THAT(stroke.samples(),
+              ElementsAre(SampleTime(down_time), SampleTime(time)));
+  ASSERT_EQ(2u, stroke.samples_seen());
 
-  // Add third sample at time = T + 10ms. An event at time = T + 8ms should be
-  // generated.
+  // Add third sample at time = T + 10ms.
   time = down_time + base::Milliseconds(10);
   sample = CreatePalmFilterSample(touch_, time, model_config_, device_info);
   stroke.ProcessSample(sample);
   ASSERT_THAT(stroke.samples(),
               ElementsAre(SampleTime(down_time),
-                          SampleTime(down_time + base::Milliseconds(8))));
-  ASSERT_EQ(2u, stroke.samples_seen());
+                          SampleTime(down_time + base::Milliseconds(4)),
+                          SampleTime(down_time + base::Milliseconds(10))));
+  ASSERT_EQ(3u, stroke.samples_seen());
 }
 
 TEST(PalmFilterStrokeTest, ResamplingTest) {
@@ -446,7 +447,7 @@
       CreatePalmFilterSample(touch_, time, model_config_, device_info);
   stroke.ProcessSample(sample2);
   // The samples should remain unchanged
-  ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
+  ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
 
   // Add third sample at time = T + 12ms. A resampled event at time = T + 8ms
   // should be generated.
@@ -458,16 +459,16 @@
   PalmFilterSample sample3 =
       CreatePalmFilterSample(touch_, time, model_config_, device_info);
   stroke.ProcessSample(sample3);
-  ASSERT_THAT(
-      stroke.samples(),
-      ElementsAre(sample1, SampleTime(down_time + base::Milliseconds(8))));
-
-  EXPECT_EQ(150, stroke.samples().back().point.x());
-  EXPECT_EQ(22, stroke.samples().back().point.y());
-  EXPECT_EQ(14, stroke.samples().back().major_radius);
-  EXPECT_EQ(13, stroke.samples().back().minor_radius);
+  ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2, sample3));
 }
 
+/**
+ * There should always be at least (max_sample_count - 1) * resample_period
+ * worth of samples. However, that's not sufficient. In the cases where the gap
+ * between samples is very large (larger than the sample horizon), there needs
+ * to be another sample in order to calculate resampled values throughout the
+ * entire range.
+ */
 TEST(PalmFilterStrokeTest, MultipleResampledValues) {
   NeuralStylusPalmDetectionFilterModelConfig model_config_;
   model_config_.max_sample_count = 3;
@@ -491,8 +492,7 @@
   // First sample should go in as is
   ASSERT_THAT(stroke.samples(), ElementsAre(sample1));
 
-  // Add second sample at time = T + 20ms. Two resampled values should be
-  // generated: 1) at time = T+8ms 2) at time = T+16ms
+  // Add second sample at time = T + 20ms.
   base::TimeTicks time = down_time + base::Milliseconds(20);
   touch_.x = 20;
   touch_.y = 30;
@@ -501,22 +501,23 @@
   PalmFilterSample sample2 =
       CreatePalmFilterSample(touch_, time, model_config_, device_info);
   stroke.ProcessSample(sample2);
-  ASSERT_THAT(stroke.samples(),
-              ElementsAre(SampleTime(down_time),
-                          SampleTime(down_time + base::Milliseconds(8)),
-                          SampleTime(down_time + base::Milliseconds(16))));
 
-  // First sample : time = T + 8ms
-  EXPECT_EQ(8, stroke.samples()[1].point.x());
-  EXPECT_EQ(18, stroke.samples()[1].point.y());
-  EXPECT_EQ(220, stroke.samples()[1].major_radius);
-  EXPECT_EQ(120, stroke.samples()[1].minor_radius);
+  ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2));
 
-  // Second sample : time = T + 16ms
-  EXPECT_EQ(16, stroke.samples().back().point.x());
-  EXPECT_EQ(26, stroke.samples().back().point.y());
-  EXPECT_EQ(220, stroke.samples().back().major_radius);
-  EXPECT_EQ(120, stroke.samples().back().minor_radius);
+  // Verify resampled sample : time = T + 8ms
+  PalmFilterSample resampled_sample =
+      stroke.GetSampleAt(down_time + base::Milliseconds(8));
+  EXPECT_EQ(8, resampled_sample.point.x());
+  EXPECT_EQ(18, resampled_sample.point.y());
+  EXPECT_EQ(220, resampled_sample.major_radius);
+  EXPECT_EQ(120, resampled_sample.minor_radius);
+
+  // Verify resampled sample : time = T + 16ms
+  resampled_sample = stroke.GetSampleAt(down_time + base::Milliseconds(16));
+  EXPECT_EQ(16, resampled_sample.point.x());
+  EXPECT_EQ(26, resampled_sample.point.y());
+  EXPECT_EQ(220, resampled_sample.major_radius);
+  EXPECT_EQ(120, resampled_sample.minor_radius);
 }
 
 }  // namespace ui