Add multiple device resampling support to InputConsumerNoResampling with tests
Added multiple device resampling support to InputConsumerNoResampling
with unit tests to ensure correctness
Bug: 297226446
Flag: EXEMPT refactor
Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="InputConsumerTest*"
Change-Id: I45528a89e0b60f46b0095078356382ed701b191b
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index cdbc186..ce8bb43 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -17,8 +17,6 @@
#define LOG_TAG "InputConsumerNoResampling"
#define ATRACE_TAG ATRACE_TAG_INPUT
-#include <chrono>
-
#include <inttypes.h>
#include <android-base/logging.h>
@@ -39,6 +37,8 @@
using std::chrono::nanoseconds;
+using android::base::Result;
+
/**
* Log debug messages relating to the consumer end of the transport channel.
* Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
@@ -169,24 +169,18 @@
msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
return msg;
}
-
-bool isPointerEvent(const MotionEvent& motionEvent) {
- return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
-}
} // namespace
-using android::base::Result;
-
// --- InputConsumerNoResampling ---
-InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
- sp<Looper> looper,
- InputConsumerCallbacks& callbacks,
- std::unique_ptr<Resampler> resampler)
+InputConsumerNoResampling::InputConsumerNoResampling(
+ const std::shared_ptr<InputChannel>& channel, sp<Looper> looper,
+ InputConsumerCallbacks& callbacks,
+ std::function<std::unique_ptr<Resampler>()> resamplerCreator)
: mChannel{channel},
mLooper{looper},
mCallbacks{callbacks},
- mResampler{std::move(resampler)},
+ mResamplerCreator{std::move(resamplerCreator)},
mFdEvents(0) {
LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
mCallback = sp<LooperEventCallback>::make(
@@ -319,7 +313,6 @@
}
void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
- // TODO(b/297226446) : add resampling
for (const InputMessage& msg : messages) {
if (msg.header.type == InputMessage::Type::MOTION) {
const int32_t action = msg.body.motion.action;
@@ -329,12 +322,31 @@
action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
(isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+
+ const bool canResample = (mResamplerCreator != nullptr) &&
+ (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER));
+ if (canResample) {
+ if (action == AMOTION_EVENT_ACTION_DOWN) {
+ if (std::unique_ptr<Resampler> resampler = mResamplerCreator();
+ resampler != nullptr) {
+ const auto [_, inserted] =
+ mResamplers.insert(std::pair(deviceId, std::move(resampler)));
+ LOG_IF(WARNING, !inserted) << deviceId << "already exists in mResamplers";
+ }
+ }
+ }
+
if (batchableEvent) {
// add it to batch
mBatches[deviceId].emplace(msg);
} else {
// consume all pending batches for this device immediately
consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
+ if (canResample &&
+ (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL)) {
+ LOG_IF(INFO, mResamplers.erase(deviceId) == 0)
+ << deviceId << "does not exist in mResamplers";
+ }
handleMessage(msg);
}
} else {
@@ -456,8 +468,13 @@
std::queue<InputMessage>& messages) {
std::unique_ptr<MotionEvent> motionEvent;
std::optional<uint32_t> firstSeqForBatch;
- const nanoseconds resampleLatency =
- (mResampler != nullptr) ? mResampler->getResampleLatency() : nanoseconds{0};
+
+ LOG_IF(FATAL, messages.empty()) << "messages queue is empty!";
+ const DeviceId deviceId = messages.front().body.motion.deviceId;
+ const auto resampler = mResamplers.find(deviceId);
+ const nanoseconds resampleLatency = (resampler != mResamplers.cend())
+ ? resampler->second->getResampleLatency()
+ : nanoseconds{0};
const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency;
while (!messages.empty() &&
@@ -474,15 +491,17 @@
}
messages.pop();
}
+
// Check if resampling should be performed.
- if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) {
- InputMessage* futureSample = nullptr;
- if (!messages.empty()) {
- futureSample = &messages.front();
- }
- mResampler->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
- futureSample);
+ InputMessage* futureSample = nullptr;
+ if (!messages.empty()) {
+ futureSample = &messages.front();
}
+ if ((motionEvent != nullptr) && (resampler != mResamplers.cend())) {
+ resampler->second->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
+ futureSample);
+ }
+
return std::make_pair(std::move(motionEvent), firstSeqForBatch);
}
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index 51fadf8..328fa68 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -247,11 +247,6 @@
void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
const InputMessage* futureSample) {
- if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
- mLatestSamples.clear();
- }
- mPreviousDeviceId = motionEvent.getDeviceId();
-
const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
updateLatestSamples(motionEvent);
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
index d708316..cbb332e 100644
--- a/libs/input/tests/InputConsumer_test.cpp
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -16,6 +16,9 @@
#include <input/InputConsumerNoResampling.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
#include <memory>
#include <optional>
@@ -25,7 +28,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/BlockingQueue.h>
+#include <input/Input.h>
#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
@@ -37,8 +42,18 @@
using ::testing::AllOf;
using ::testing::Matcher;
-using ::testing::Not;
+struct Pointer {
+ int32_t id{0};
+ ToolType toolType{ToolType::FINGER};
+ float x{0.0f};
+ float y{0.0f};
+ bool isResampled{false};
+
+ PointerBuilder asPointerBuilder() const {
+ return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+ }
+};
} // namespace
class InputConsumerTest : public testing::Test, public InputConsumerCallbacks {
@@ -47,9 +62,9 @@
: mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
Looper::setForThread(mLooper);
- mConsumer =
- std::make_unique<InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
- std::make_unique<LegacyResampler>());
+ mConsumer = std::make_unique<
+ InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+ []() { return std::make_unique<LegacyResampler>(); });
}
void invokeLooperCallback() const {
@@ -71,6 +86,9 @@
EXPECT_THAT(*motionEvent, matcher);
}
+ InputMessage nextPointerMessage(std::chrono::nanoseconds eventTime, DeviceId deviceId,
+ int32_t action, const Pointer& pointer);
+
std::shared_ptr<TestInputChannel> mClientTestChannel;
sp<Looper> mLooper;
std::unique_ptr<InputConsumerNoResampling> mConsumer;
@@ -83,6 +101,7 @@
BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
private:
+ uint32_t mLastSeq{0};
size_t mOnBatchedInputEventPendingInvocationCount{0};
// InputConsumerCallbacks interface
@@ -118,6 +137,19 @@
};
};
+InputMessage InputConsumerTest::nextPointerMessage(std::chrono::nanoseconds eventTime,
+ DeviceId deviceId, int32_t action,
+ const Pointer& pointer) {
+ ++mLastSeq;
+ return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+ .eventTime(eventTime.count())
+ .deviceId(deviceId)
+ .source(AINPUT_SOURCE_TOUCHSCREEN)
+ .action(action)
+ .pointer(pointer.asPointerBuilder())
+ .build();
+}
+
TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
.eventTime(nanoseconds{0ms}.count())
@@ -235,8 +267,7 @@
.build());
invokeLooperCallback();
- assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
- Not(MotionEventIsResampled())));
+ assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
@@ -244,4 +275,121 @@
mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
}
+
+/**
+ * The test supposes a 60Hz Vsync rate and a 200Hz input rate. The InputMessages are intertwined as
+ * in a real use cases. The test's two devices should be resampled independently. Moreover, the
+ * InputMessage stream layout for the test is:
+ *
+ * DOWN(0, 0ms)
+ * MOVE(0, 5ms)
+ * MOVE(0, 10ms)
+ * DOWN(1, 15ms)
+ *
+ * CONSUME(16ms)
+ *
+ * MOVE(1, 20ms)
+ * MOVE(1, 25ms)
+ * MOVE(0, 30ms)
+ *
+ * CONSUME(32ms)
+ *
+ * MOVE(0, 35ms)
+ * UP(1, 40ms)
+ * UP(0, 45ms)
+ *
+ * CONSUME(48ms)
+ *
+ * The first field is device ID, and the second field is event time.
+ */
+TEST_F(InputConsumerTest, MultiDeviceResampling) {
+ mClientTestChannel->enqueueMessage(nextPointerMessage(0ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_DOWN,
+ Pointer{.x = 0, .y = 0}));
+
+ mClientTestChannel->assertNoSentMessages();
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSampleCount(1)));
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(5ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 1.0f, .y = 2.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(10ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 2.0f, .y = 4.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(15ms, /*deviceId=*/1,
+ AMOTION_EVENT_ACTION_DOWN,
+ Pointer{.x = 10.0f, .y = 10.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSampleCount(1)));
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{11ms,
+ {PointerArgs{.x = 2.2f, .y = 4.4f, .isResampled = true}}})));
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(20ms, /*deviceId=*/1,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 11.0f, .y = 12.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(25ms, /*deviceId=*/1,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 12.0f, .y = 14.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(30ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 5.0f, .y = 6.0f}));
+
+ invokeLooperCallback();
+ assertOnBatchedInputEventPendingWasCalled();
+ mConsumer->consumeBatchedInputEvents(32'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{27ms,
+ {PointerArgs{.x = 12.4f, .y = 14.8f, .isResampled = true}}})));
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(35ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_MOVE,
+ Pointer{.x = 8.0f, .y = 9.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(40ms, /*deviceId=*/1,
+ AMOTION_EVENT_ACTION_UP,
+ Pointer{.x = 12.0f, .y = 14.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(45ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_UP,
+ Pointer{.x = 8.0f, .y = 9.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(48'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{37'500'000ns,
+ {PointerArgs{.x = 9.5f, .y = 10.5f, .isResampled = true}}})));
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+ // The sequence order is based on the expected consumption. Each sequence number corresponds to
+ // one of the previously enqueued messages.
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/9, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/7, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/8, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/10, /*handled=*/true);
+}
} // namespace android
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 26dee39..fae8518 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -87,7 +87,6 @@
struct InputStream {
std::vector<InputSample> samples{};
int32_t action{0};
- DeviceId deviceId{0};
/**
* Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
* the relevant data for tests.
@@ -100,8 +99,8 @@
MotionEventBuilder motionEventBuilder =
MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
.downTime(0)
- .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
- .deviceId(deviceId);
+ .eventTime(
+ static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count());
for (const Pointer& pointer : firstSample.pointers) {
const PointerBuilder pointerBuilder =
PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
@@ -289,28 +288,6 @@
assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
}
-TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
- MotionEvent motionFromFirstDevice =
- InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
- InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
- AMOTION_EVENT_ACTION_MOVE,
- .deviceId = 0};
-
- mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
-
- MotionEvent motionFromSecondDevice =
- InputStream{{InputSample{11ms,
- {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
- AMOTION_EVENT_ACTION_MOVE,
- .deviceId = 1};
- const MotionEvent originalMotionEvent = motionFromSecondDevice;
-
- mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
- // The MotionEvent should not be resampled because the second event came from a different device
- // than the previous event.
- assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
-}
-
TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
MotionEvent motionEvent =
InputStream{{InputSample{10ms,
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
index dd2e40c..3589de5 100644
--- a/libs/input/tests/TestEventMatchers.h
+++ b/libs/input/tests/TestEventMatchers.h
@@ -16,18 +16,39 @@
#pragma once
+#include <chrono>
#include <ostream>
+#include <vector>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
#include <input/Input.h>
namespace android {
+namespace {
+
+using ::testing::Matcher;
+
+} // namespace
+
/**
* This file contains a copy of Matchers from .../inputflinger/tests/TestEventMatchers.h. Ideally,
* implementations must not be duplicated.
* TODO(b/365606513): Find a way to share TestEventMatchers.h between inputflinger and libinput.
*/
+struct PointerArgs {
+ float x{0.0f};
+ float y{0.0f};
+ bool isResampled{false};
+};
+
+struct Sample {
+ std::chrono::nanoseconds eventTime{0};
+ std::vector<PointerArgs> pointers{};
+};
+
class WithDeviceIdMatcher {
public:
using is_gtest_matcher = void;
@@ -79,32 +100,88 @@
return WithMotionActionMatcher(action);
}
-class MotionEventIsResampledMatcher {
+class WithSampleCountMatcher {
public:
using is_gtest_matcher = void;
+ explicit WithSampleCountMatcher(size_t sampleCount) : mExpectedSampleCount{sampleCount} {}
bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream*) const {
- const size_t numSamples = motionEvent.getHistorySize() + 1;
- const size_t numPointers = motionEvent.getPointerCount();
- if (numPointers <= 0 || numSamples <= 0) {
+ return (motionEvent.getHistorySize() + 1) == mExpectedSampleCount;
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "sample count " << mExpectedSampleCount; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "different sample count"; }
+
+private:
+ const size_t mExpectedSampleCount;
+};
+
+inline WithSampleCountMatcher WithSampleCount(size_t sampleCount) {
+ return WithSampleCountMatcher(sampleCount);
+}
+
+class WithSampleMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithSampleMatcher(size_t sampleIndex, const Sample& sample)
+ : mSampleIndex{sampleIndex}, mSample{sample} {}
+
+ bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream* os) const {
+ if (motionEvent.getHistorySize() < mSampleIndex) {
+ *os << "sample index out of bounds";
return false;
}
- for (size_t i = 0; i < numPointers; ++i) {
+
+ if (motionEvent.getHistoricalEventTime(mSampleIndex) != mSample.eventTime.count()) {
+ *os << "event time mismatch. sample: "
+ << motionEvent.getHistoricalEventTime(mSampleIndex)
+ << " expected: " << mSample.eventTime.count();
+ return false;
+ }
+
+ if (motionEvent.getPointerCount() != mSample.pointers.size()) {
+ *os << "pointer count mismatch. sample: " << motionEvent.getPointerCount()
+ << " expected: " << mSample.pointers.size();
+ return false;
+ }
+
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+ ++pointerIndex) {
const PointerCoords& pointerCoords =
- motionEvent.getSamplePointerCoords()[numSamples * numPointers + i];
- if (!pointerCoords.isResampled) {
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, mSampleIndex));
+ if ((pointerCoords.getX() != mSample.pointers[pointerIndex].x) ||
+ (pointerCoords.getY() != mSample.pointers[pointerIndex].y)) {
+ *os << "sample coordinates mismatch at pointer index " << pointerIndex
+ << ". sample: (" << pointerCoords.getX() << ", " << pointerCoords.getY()
+ << ") expected: (" << mSample.pointers[pointerIndex].x << ", "
+ << mSample.pointers[pointerIndex].y << ")";
+ return false;
+ }
+ if (motionEvent.isResampled(pointerIndex, mSampleIndex) !=
+ mSample.pointers[pointerIndex].isResampled) {
+ *os << "resampling flag mismatch. sample: "
+ << motionEvent.isResampled(pointerIndex, mSampleIndex)
+ << " expected: " << mSample.pointers[pointerIndex].isResampled;
return false;
}
}
return true;
}
- void DescribeTo(std::ostream* os) const { *os << "MotionEvent is resampled."; }
+ void DescribeTo(std::ostream* os) const { *os << "motion event sample properties match."; }
- void DescribeNegationTo(std::ostream* os) const { *os << "MotionEvent is not resampled."; }
+ void DescribeNegationTo(std::ostream* os) const {
+ *os << "motion event sample properties do not match expected properties.";
+ }
+
+private:
+ const size_t mSampleIndex;
+ const Sample mSample;
};
-inline MotionEventIsResampledMatcher MotionEventIsResampled() {
- return MotionEventIsResampledMatcher();
+inline WithSampleMatcher WithSample(size_t sampleIndex, const Sample& sample) {
+ return WithSampleMatcher(sampleIndex, sample);
}
+
} // namespace android