/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "InputData.h" #include "Units.h" #include "gtest/gtest.h" #include "mozilla/Maybe.h" #include "mozilla/TimeStamp.h" #include "TouchResampler.h" using namespace mozilla; using widget::TouchResampler; class TouchResamplerTest : public ::testing::Test { protected: virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); } TimeStamp Time(double aMilliseconds) { return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds); } uint64_t ProcessEvent( MultiTouchInput::MultiTouchType aType, std::initializer_list> aHistoricalData, const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) { MultiTouchInput input(aType, 0, aTimeStamp, 0); input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f)); for (const auto& histData : aHistoricalData) { input.mTouches[0].mHistoricalData.AppendElement( SingleTouchData::HistoricalTouchData{ histData.first, histData.second, {}, {}, 0.0f, 0.0f}); } return resampler.ProcessEvent(std::move(input)); } void CheckTime(const TimeStamp& aTimeStamp, const TimeStamp& aExpectedTimeStamp) { EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(), (aExpectedTimeStamp - baseTimeStamp).ToMilliseconds()); } void CheckEvent(const MultiTouchInput& aEvent, MultiTouchInput::MultiTouchType aExpectedType, std::initializer_list> aExpectedHistoricalData, const TimeStamp& aExpectedTimeStamp, const ScreenIntPoint& aExpectedPosition) { EXPECT_EQ(aEvent.mType, aExpectedType); EXPECT_EQ(aEvent.mTouches.Length(), size_t(1)); EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(), aExpectedHistoricalData.size()); for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) { CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp, aExpectedHistoricalData.begin()[i].first); EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint, aExpectedHistoricalData.begin()[i].second); } CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp); EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition); } struct ExpectedOutgoingEvent { Maybe mEventId; MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START; std::initializer_list> mHistoricalData; TimeStamp mTimeStamp; ScreenIntPoint mPosition; }; void CheckOutgoingEvents( std::initializer_list aExpectedEvents) { auto outgoing = resampler.ConsumeOutgoingEvents(); EXPECT_EQ(outgoing.size(), aExpectedEvents.size()); for (const auto& expectedEvent : aExpectedEvents) { auto outgoingEvent = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId); CheckEvent(outgoingEvent.mEvent, expectedEvent.mType, expectedEvent.mHistoricalData, expectedEvent.mTimeStamp, expectedEvent.mPosition); } } TimeStamp baseTimeStamp; TouchResampler resampler; }; TEST_F(TouchResamplerTest, BasicExtrapolation) { // Execute the following sequence: // // 0----------10-------16-----20---------------32------------ // * touchstart at (10, 10) // * touchmove at (20, 20) // * frame // * touchend at (20, 20) // * frame // // And expect the following output: // // 0----------10-------16-----20---------------32------------ // * touchstart at (10, 10) // * touchmove at (26, 26) // * touchmove at (20, 20) // * touchend at (20, 20) // // The first frame should emit an extrapolated touchmove from the position // data in the touchstart and touchmove events. // The touchend should force a synthesized touchmove that returns back to a // non-resampled position. EXPECT_FALSE(resampler.InTouchingState()); auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)); EXPECT_TRUE(resampler.InTouchingState()); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(20, 20)); resampler.NotifyFrame(Time(16.0)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(20, 20)}}, Time(16.0), ScreenIntPoint(26, 26)}, }); auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), ScreenIntPoint(20, 20)); EXPECT_FALSE(resampler.InTouchingState()); CheckOutgoingEvents({ {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(16.0), ScreenIntPoint(20, 20)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), ScreenIntPoint(20, 20)}, }); // No more events should be produced from here on out. resampler.NotifyFrame(Time(32.0)); auto outgoing = resampler.ConsumeOutgoingEvents(); EXPECT_TRUE(outgoing.empty()); } TEST_F(TouchResamplerTest, BasicInterpolation) { // Same test as BasicExtrapolation, but with a frame time that's 10ms earlier. // // Execute the following sequence: // // 0------6---10-----------20--22------------30------------- // * touchstart at (10, 10) // * touchmove at (20, 20) // * frame // * touchend at (20, 20) // * frame // // And expect the following output: // // 0------6---10-----------20--22------------30------------- // * touchstart at (10, 10) // * touchmove (16, 16) // * touchmove (20, 20) // * touchend at (20, 20) // // The first frame should emit an interpolated touchmove from the position // data in the touchstart and touchmove events. // The touchend should create a touchmove that returns back to a non-resampled // position. auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)); EXPECT_TRUE(resampler.InTouchingState()); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(20, 20)); resampler.NotifyFrame(Time(6.0)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(6.0), ScreenIntPoint(16, 16)}, }); auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), ScreenIntPoint(20, 20)); EXPECT_FALSE(resampler.InTouchingState()); CheckOutgoingEvents({ {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(20, 20)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(20.0), ScreenIntPoint(20, 20)}, }); // No more events should be produced from here on out. resampler.NotifyFrame(Time(22.0)); auto outgoing = resampler.ConsumeOutgoingEvents(); EXPECT_TRUE(outgoing.empty()); } TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) { // Interpolate from the historical data in a touch move event. // // Execute the following sequence: // // 0----------10-------16-----20-----------30--32------------ // * touchstart at (10, 10) // * [hist] at (20, 25) for // `---------------* touchmove at (30, 30) // * frame // * touchend at (30, 30) // * frame // // And expect the following output: // // 0----------10-------16-----20-----------30--32------------ // * touchstart at (10, 10) // * [hist] at (20, 25) for // `--------* touchmove at (26, 28) // * touchmove at (30, 30) // * touchend at (30, 30) // // The first frame should emit an interpolated touchmove from the position // data in the touchmove event, and integrate the historical data point into // the resampled event. // The touchend should force a synthesized touchmove that returns back to a // non-resampled position. // This also tests that interpolation works for both x and y, by giving the // historical datapoint different values for x and y. // (26, 28) is 60% of the way from (20, 25) to (30, 30). auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(20.0), ScreenIntPoint(30, 30)); resampler.NotifyFrame(Time(16.0)); auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0), ScreenIntPoint(30, 30)); resampler.NotifyFrame(Time(32.0)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0), ScreenIntPoint(26, 28)}, {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(20.0), ScreenIntPoint(30, 30)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(30.0), ScreenIntPoint(30, 30)}, }); } TEST_F(TouchResamplerTest, MultipleTouches) { EXPECT_FALSE(resampler.InTouchingState()); // Touch start MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0), 0); inputStart0.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f)); auto idStart0 = resampler.ProcessEvent(std::move(inputStart0)); EXPECT_TRUE(resampler.InTouchingState()); // Touch move MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0), 0); inputMove1.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f)); inputMove1.mTouches[0].mHistoricalData.AppendElement( SingleTouchData::HistoricalTouchData{ Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f}); auto idMove1 = resampler.ProcessEvent(std::move(inputMove1)); EXPECT_TRUE(resampler.InTouchingState()); // Frame resampler.NotifyFrame(Time(16.0)); // Touch move MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0), 0); inputMove2.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f)); auto idMove2 = resampler.ProcessEvent(std::move(inputMove2)); EXPECT_TRUE(resampler.InTouchingState()); // Touch start MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0), 0); inputStart3.mTouches.AppendElement( SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f)); inputStart3.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f)); auto idStart3 = resampler.ProcessEvent(std::move(inputStart3)); EXPECT_TRUE(resampler.InTouchingState()); // Touch move MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0), 0); inputMove4.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f)); inputMove4.mTouches.AppendElement( SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f)); auto idMove4 = resampler.ProcessEvent(std::move(inputMove4)); EXPECT_TRUE(resampler.InTouchingState()); // Frame resampler.NotifyFrame(Time(32.0)); // Touch move MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0), 0); inputMove5.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f)); inputMove5.mTouches.AppendElement( SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f)); auto idMove5 = resampler.ProcessEvent(std::move(inputMove5)); EXPECT_TRUE(resampler.InTouchingState()); // Touch end MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0); // Touch point with identifier 1 is lifted inputEnd6.mTouches.AppendElement( SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f)); auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6)); EXPECT_TRUE(resampler.InTouchingState()); // Frame resampler.NotifyFrame(Time(48.0)); // Touch move MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0), 0); inputMove7.mTouches.AppendElement( SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f)); auto idMove7 = resampler.ProcessEvent(std::move(inputMove7)); EXPECT_TRUE(resampler.InTouchingState()); // Frame resampler.NotifyFrame(Time(64.0)); // Touch end MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0); // Touch point with identifier 2 is lifted inputEnd8.mTouches.AppendElement( SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f)); auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8)); EXPECT_FALSE(resampler.InTouchingState()); // Check outgoing events auto outgoing = resampler.ConsumeOutgoingEvents(); EXPECT_EQ(outgoing.size(), size_t(9)); auto outgoingStart0 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0)); CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)); auto outgoingMove1 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1)); // (26, 28) is 60% of the way from (20, 25) to (30, 30). CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0), ScreenIntPoint(26, 28)); auto outgoingMove2 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2)); CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0), ScreenIntPoint(30, 40)); auto outgoingStart3 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3)); EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START); CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0)); EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2)); // touch order should be taken from the original touch start event EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2); EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint, ScreenIntPoint(100, 10)); EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1); EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint, ScreenIntPoint(30, 40)); auto outgoingMove4 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4)); EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE); CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0)); EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2)); // Touch order should be taken from the original touch move event. // Both touches should be resampled. EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1); EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint, ScreenIntPoint(30, 42)); EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2); EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint, ScreenIntPoint(100, 14)); auto outgoingMove5 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5)); EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE); CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0)); EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2)); // touch order should be taken from the original touch move event EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1); EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint, ScreenIntPoint(30, 60)); EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(), size_t(1)); CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp, Time(40.0)); EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint, ScreenIntPoint(30, 50)); EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2); EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint, ScreenIntPoint(100, 40)); EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(), size_t(1)); CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp, Time(40.0)); EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint, ScreenIntPoint(100, 30)); auto outgoingEnd6 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6)); CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {}, Time(50.0), ScreenIntPoint(30, 60)); auto outgoingMove7 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7)); // No extrapolation because the frame at 64.0 cleared the data points because // there was no pending touch move event at that point CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {}, Time(60.0), ScreenIntPoint(100, 60)); EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2); auto outgoingEnd8 = std::move(outgoing.front()); outgoing.pop(); EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8)); CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(100, 60)); } TEST_F(TouchResamplerTest, MovingPauses) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(20, 20)); resampler.NotifyFrame(Time(16.0)); auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0), ScreenIntPoint(40, 40)); resampler.NotifyFrame(Time(32.0)); auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), ScreenIntPoint(50, 40)); resampler.NotifyFrame(Time(48.0)); resampler.NotifyFrame(Time(64.0)); auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(50, 40)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(10, 10)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(20, 20)}}, Time(16.0), ScreenIntPoint(26, 26)}, {Some(idMove2), MultiTouchInput::MULTITOUCH_MOVE, {{Time(30.0), ScreenIntPoint(40, 40)}}, Time(32.0), ScreenIntPoint(42, 42)}, {Some(idMove3), MultiTouchInput::MULTITOUCH_MOVE, {{Time(40.0), ScreenIntPoint(50, 40)}}, Time(48.0), ScreenIntPoint(58, 40)}, // There was no event between two frames here, so we expect a reset event, // so that we pause at a non-resampled position. {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(48.0), ScreenIntPoint(50, 40)}, {Some(idEnd4), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(50, 40)}, }); } TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(0, 10)); resampler.NotifyFrame(Time(11.0)); // 16 - 5 auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0), ScreenIntPoint(0, 30)); resampler.NotifyFrame(Time(27.0)); // 32 - 5 auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), ScreenIntPoint(0, 40)); resampler.NotifyFrame(Time(43.0)); // 48 - 5 auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0), ScreenIntPoint(0, 60)); resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(11.0), ScreenIntPoint(0, 11)}, {Some(idMove2), MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(27.0), ScreenIntPoint(0, 27)}, {Some(idMove3), MultiTouchInput::MULTITOUCH_MOVE, {{Time(30.0), ScreenIntPoint(0, 30)}, {Time(40.0), ScreenIntPoint(0, 40)}}, Time(43.0), ScreenIntPoint(0, 43)}, {Some(idMove4), MultiTouchInput::MULTITOUCH_MOVE, {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(59.0), ScreenIntPoint(0, 59)}, {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(60.0), ScreenIntPoint(0, 60)}, {Some(idEnd5), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)}, }); } TEST_F(TouchResamplerTest, MultipleMoveEvents) { // Test what happens if multiple touch move events appear between two frames. // This scenario shouldn't occur on Android but we should be able to deal with // it anyway. Check that we don't discard any event IDs. auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0), ScreenIntPoint(0, 10)); resampler.NotifyFrame(Time(11.0)); // 16 - 5 auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0), ScreenIntPoint(0, 30)); auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), ScreenIntPoint(0, 40)); auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0), ScreenIntPoint(0, 50)); auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0), ScreenIntPoint(0, 55)); resampler.NotifyFrame(Time(43.0)); // 48 - 5 resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(11.0), ScreenIntPoint(0, 11)}, {Some(idMove2), MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0), ScreenIntPoint(0, 30)}, {Some(idMove3), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0), ScreenIntPoint(0, 40)}, {Some(idMove4), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(43.0), ScreenIntPoint(0, 43)}, {Some(idMove5), MultiTouchInput::MULTITOUCH_MOVE, {{Time(45.0), ScreenIntPoint(0, 45)}, {Time(50.0), ScreenIntPoint(0, 50)}, {Time(55.0), ScreenIntPoint(0, 55)}}, Time(59.0), ScreenIntPoint(0, 59)}, {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(59.0), ScreenIntPoint(0, 55)}, {Some(idEnd5), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)}, }); } TEST_F(TouchResamplerTest, LimitFuturePrediction) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); // Fingers move until time 44, then pause. UI thread is occupied until 64. auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}, {Time(32.0), ScreenIntPoint(0, 32)}}, Time(44.0), ScreenIntPoint(0, 44)); resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, // kTouchResampleMaxPredictMs == 8 // Refuse to predict more than 8ms into the future, the fingers might have // paused. Make an event for time 52 (= 44 + 8) instead of 59. {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}, {Time(32.0), ScreenIntPoint(0, 32)}, {Time(44.0), ScreenIntPoint(0, 44)}}, Time(52.0), ScreenIntPoint(0, 52)}, {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(52.0), ScreenIntPoint(0, 44)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)}, }); } TEST_F(TouchResamplerTest, LimitBacksampling) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); // Fingers move until time 44, then pause. UI thread is occupied until 64. // Then we get a frame callback with a very outdated frametime. auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}, {Time(32.0), ScreenIntPoint(0, 32)}}, Time(44.0), ScreenIntPoint(0, 44)); resampler.NotifyFrame(Time(11.0)); // 16 - 5 auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, // kTouchResampleMaxBacksampleMs == 20 // Refuse to sample further back than 20ms before the last data point. // Make an event for time 24 (= 44 - 20) instead of time 11. {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(24.0), ScreenIntPoint(0, 24)}, {Nothing(), MultiTouchInput::MULTITOUCH_MOVE, {{Time(32.0), ScreenIntPoint(0, 32)}}, Time(44.0), ScreenIntPoint(0, 44)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)}, }); } TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); // Fingers move until time 40, then pause. UI thread is occupied until 64. auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}, {Time(30.0), ScreenIntPoint(0, 30)}}, Time(40.0), ScreenIntPoint(0, 40)); resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, // kTouchResampleOldTouchThresholdMs == 17 // Refuse to extrapolate from a data point that's more than 17ms older // than the frame time. {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(20.0), ScreenIntPoint(0, 20)}, {Time(30.0), ScreenIntPoint(0, 30)}}, Time(40.0), ScreenIntPoint(0, 40)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 44)}, }); } TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); // Fingers move until time 10, pause, and move again at 55. // UI thread is occupied until 64. auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0), ScreenIntPoint(0, 55)); resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, // kTouchResampleWindowSize == 40 // Refuse to resample between two data points that are more than 40ms // apart. {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0), ScreenIntPoint(0, 55)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)}, }); } TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); // Fingers move until time 10, pause, and move again at 60. // UI thread is occupied until 64. auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0), ScreenIntPoint(0, 60)); resampler.NotifyFrame(Time(59.0)); // 64 - 5 auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, // kTouchResampleWindowSize == 40 // Refuse to resample between two data points that are more than 40ms // apart. {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0), ScreenIntPoint(0, 60)}, {Some(idEnd2), MultiTouchInput::MULTITOUCH_END, {}, Time(70.0), ScreenIntPoint(0, 60)}, }); } TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) { auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)); auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0), ScreenIntPoint(0, 16)); resampler.NotifyFrame(Time(20.0)); auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0), ScreenIntPoint(0, 25)); auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0), ScreenIntPoint(0, 25)); CheckOutgoingEvents({ {Some(idStart0), MultiTouchInput::MULTITOUCH_START, {}, Time(0.0), ScreenIntPoint(0, 0)}, {Some(idMove1), MultiTouchInput::MULTITOUCH_MOVE, {{Time(10.0), ScreenIntPoint(0, 10)}, {Time(16.0), ScreenIntPoint(0, 16)}}, Time(20.0), ScreenIntPoint(0, 20)}, // Discard the historical data point from time 18, because we've already // sent out an event with time 20 and don't want to go back before that. {Some(idMove2), MultiTouchInput::MULTITOUCH_MOVE, {}, Time(25.0), ScreenIntPoint(0, 25)}, {Some(idEnd3), MultiTouchInput::MULTITOUCH_END, {}, Time(35.0), ScreenIntPoint(0, 25)}, }); }