diff options
author | 2023-08-23 17:20:13 +0000 | |
---|---|---|
committer | 2023-09-12 15:45:03 +0000 | |
commit | 8c7cb5935292b2a887619a9f4a2265ac61720aa5 (patch) | |
tree | f57646b6fae93e6e7ee8f2bae594262d3a28aa57 | |
parent | 3821b0df36c612755b711d8d5a7b63377d7cf5d9 (diff) |
TouchpadInputMapper: add timer provider
Adding a timer provider allows the Gestures library to perform some
asynchronous tasks, including ones which improve tap-to-click detection.
Bug: 297192727
Test: set the
persist.device_config.aconfig_flags.input.com.android.input.flags.enable_gestures_library_timer_provider
sysprop to true, restart, then make fast tap-to-click gestures
(where the finger is contacting for fewer than 3 frames) on the
touchpad, and check they're reported immediately
Change-Id: Ib9b8dacc71c88b6fd47bdd747f90ef6a44b37cc4
-rw-r--r-- | include/input/PrintTools.h | 21 | ||||
-rw-r--r-- | libs/input/input_flags.aconfig | 7 | ||||
-rw-r--r-- | services/inputflinger/reader/Android.bp | 1 | ||||
-rw-r--r-- | services/inputflinger/reader/InputReader.cpp | 1 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 49 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchpadInputMapper.h | 4 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/TimerProvider.cpp | 151 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/TimerProvider.h | 88 | ||||
-rw-r--r-- | services/inputflinger/tests/Android.bp | 1 | ||||
-rw-r--r-- | services/inputflinger/tests/TimerProvider_test.cpp | 311 |
10 files changed, 604 insertions, 30 deletions
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 0e3fbb1982..63c0e40e4e 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -75,11 +75,12 @@ std::string dumpSet(const std::set<T>& v, std::string (*toString)(const T&) = co } /** - * Convert a map to string. Both keys and values of the map should be integral type. + * Convert a map or multimap to string. Both keys and values of the map should be integral type. */ -template <typename K, typename V> -std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString, - std::string (*valueToString)(const V&) = constToString) { +template <typename T> +std::string dumpMap(const T& map, + std::string (*keyToString)(const typename T::key_type&) = constToString, + std::string (*valueToString)(const typename T::mapped_type&) = constToString) { std::string out; for (const auto& [k, v] : map) { if (!out.empty()) { @@ -104,15 +105,13 @@ std::string dumpMapKeys(const std::map<K, V>& map, return out.empty() ? "{}" : (out + "}"); } -/** - * Convert a vector to a string. The values of the vector should be of a type supported by - * constToString. - */ +/** Convert a vector to a string. */ template <typename T> -std::string dumpVector(std::vector<T> values) { - std::string dump = constToString(values[0]); +std::string dumpVector(const std::vector<T>& values, + std::string (*valueToString)(const T&) = constToString) { + std::string dump = valueToString(values[0]); for (size_t i = 1; i < values.size(); i++) { - dump += ", " + constToString(values[i]); + dump += ", " + valueToString(values[i]); } return dump; } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 2af5a4a5ae..a0563f916d 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -20,3 +20,10 @@ flag { description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons" bug: "293587049" } + +flag { + name: "enable_gestures_library_timer_provider" + namespace: "input" + description: "Set to true to enable timer support for the touchpad Gestures library" + bug: "297192727" +} diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 8fe6411aeb..e1806a0a6c 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -69,6 +69,7 @@ filegroup { "mapper/gestures/HardwareProperties.cpp", "mapper/gestures/HardwareStateConverter.cpp", "mapper/gestures/PropertyProvider.cpp", + "mapper/gestures/TimerProvider.cpp", ], } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 0aea0b3893..5766b14231 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -946,6 +946,7 @@ void InputReader::dump(std::string& dump) { device->dump(dump, eventHubDevStr); } + dump += StringPrintf(INDENT "NextTimeout: %" PRId64 "\n", mNextTimeout); dump += INDENT "Configuration:\n"; dump += INDENT2 "ExcludedDeviceNames: ["; for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 6ea004df13..3e5e01e776 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -25,6 +25,7 @@ #include <android-base/stringprintf.h> #include <android/input.h> +#include <com_android_input_flags.h> #include <ftl/enum.h> #include <input/PrintTools.h> #include <linux/input-event-codes.h> @@ -34,8 +35,11 @@ #include "TouchCursorInputMapperCommon.h" #include "TouchpadInputMapper.h" #include "gestures/HardwareProperties.h" +#include "gestures/TimerProvider.h" #include "ui/Rotation.h" +namespace input_flags = com::android::input::flags; + namespace android { namespace { @@ -232,6 +236,7 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, : InputMapper(deviceContext, readerConfig), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), + mTimerProvider(*getContext()), mStateConverter(deviceContext, mMotionAccumulator), mGestureConverter(*getContext(), deviceContext, getDeviceId()), mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()), @@ -253,8 +258,12 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, // 2) TouchpadInputMapper is stored as a unique_ptr and not moved. mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider), &mPropertyProvider); + if (input_flags::enable_gestures_library_timer_provider()) { + mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>( + &kGestureTimerProvider), + &mTimerProvider); + } mGestureInterpreter->SetCallback(gestureInterpreterCallback, this); - // TODO(b/251196347): set a timer provider, so the library can use timers. } TouchpadInputMapper::~TouchpadInputMapper() { @@ -262,14 +271,14 @@ TouchpadInputMapper::~TouchpadInputMapper() { mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } - // The gesture interpreter's destructor will call its property provider's free function for all - // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer - // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may - // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on - // declaration order to avoid crashes seems rather fragile, so explicitly clear the property - // provider here to ensure all the freeProperty calls happen before mPropertyProvider is - // destructed. + // The gesture interpreter's destructor will try to free its property and timer providers, + // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers. + // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already + // been freed, causing allocation errors or use-after-free bugs. Depending on declaration order + // to avoid this seems rather fragile, so explicitly clear the providers here to ensure all the + // freeProperty and freeTimer calls happen before the providers are destructed. mGestureInterpreter->SetPropProvider(nullptr, nullptr); + mGestureInterpreter->SetTimerProvider(nullptr, nullptr); } uint32_t TouchpadInputMapper::getSources() const { @@ -287,9 +296,6 @@ void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) { void TouchpadInputMapper::dump(std::string& dump) { dump += INDENT2 "Touchpad Input Mapper:\n"; - if (mProcessing) { - dump += INDENT3 "Currently processing a hardware state\n"; - } if (mResettingInterpreter) { dump += INDENT3 "Currently resetting gesture interpreter\n"; } @@ -298,6 +304,12 @@ void TouchpadInputMapper::dump(std::string& dump) { dump += addLinePrefix(mGestureConverter.dump(), INDENT4); dump += INDENT3 "Gesture properties:\n"; dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); + if (input_flags::enable_gestures_library_timer_provider()) { + dump += INDENT3 "Timer provider:\n"; + dump += addLinePrefix(mTimerProvider.dump(), INDENT4); + } else { + dump += INDENT3 "Timer provider: disabled by flag\n"; + } dump += INDENT3 "Captured event converter:\n"; dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4); dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str()); @@ -443,13 +455,18 @@ void TouchpadInputMapper::updatePalmDetectionMetrics() { std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs) { ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str()); - mProcessing = true; mGestureInterpreter->PushHardwareState(&schs.state); - mProcessing = false; - return processGestures(when, readTime); } +std::list<NotifyArgs> TouchpadInputMapper::timeoutExpired(nsecs_t when) { + if (!input_flags::enable_gestures_library_timer_provider()) { + return {}; + } + mTimerProvider.triggerCallbacks(when); + return processGestures(when, when); +} + void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str()); if (mResettingInterpreter) { @@ -457,10 +474,6 @@ void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { // ignore any gestures produced from the interpreter while we're resetting it. return; } - if (!mProcessing) { - ALOGE("Received gesture outside of the normal processing flow; ignoring it."); - return; - } mGesturesToProcess.push_back(*gesture); } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 47d712ef27..a68ae43912 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -34,6 +34,7 @@ #include "gestures/GestureConverter.h" #include "gestures/HardwareStateConverter.h" #include "gestures/PropertyProvider.h" +#include "gestures/TimerProvider.h" #include "include/gestures.h" @@ -56,6 +57,7 @@ public: ConfigurationChanges changes) override; [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override; [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override; void consumeGesture(const Gesture* gesture); @@ -80,6 +82,7 @@ private: std::shared_ptr<PointerControllerInterface> mPointerController; PropertyProvider mPropertyProvider; + TimerProvider mTimerProvider; // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches @@ -92,7 +95,6 @@ private: CapturedTouchpadEventConverter mCapturedEventConverter; bool mPointerCaptured = false; - bool mProcessing = false; bool mResettingInterpreter = false; std::vector<Gesture> mGesturesToProcess; diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp new file mode 100644 index 0000000000..df2f260098 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimerProvider.h" + +#include <chrono> +#include <string> + +#include <android-base/logging.h> +#include <input/PrintTools.h> + +namespace android { + +namespace { + +nsecs_t stimeToNsecs(stime_t time) { + return std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::duration<stime_t>(time)) + .count(); +} + +stime_t nsecsToStime(nsecs_t time) { + return std::chrono::duration_cast<std::chrono::duration<stime_t>>( + std::chrono::nanoseconds(time)) + .count(); +} + +GesturesTimer* createTimer(void* data) { + return static_cast<TimerProvider*>(data)->createTimer(); +} + +void setDeadline(void* data, GesturesTimer* timer, stime_t delay, GesturesTimerCallback callback, + void* callbackData) { + static_cast<TimerProvider*>(data)->setDeadline(timer, stimeToNsecs(delay), callback, + callbackData); +}; + +void cancelTimer(void* data, GesturesTimer* timer) { + static_cast<TimerProvider*>(data)->cancelTimer(timer); +} + +void freeTimer(void* data, GesturesTimer* timer) { + static_cast<TimerProvider*>(data)->freeTimer(timer); +} + +} // namespace + +const GesturesTimerProvider kGestureTimerProvider = { + .create_fn = createTimer, + .set_fn = setDeadline, + .cancel_fn = cancelTimer, + .free_fn = freeTimer, +}; + +TimerProvider::TimerProvider(InputReaderContext& context) : mReaderContext(context) {} + +std::string TimerProvider::dump() { + std::string dump; + auto timerPtrToString = [](const std::unique_ptr<GesturesTimer>& timer) { + return std::to_string(timer->id); + }; + dump += "Timer IDs: " + dumpVector<std::unique_ptr<GesturesTimer>>(mTimers, timerPtrToString) + + "\n"; + dump += "Deadlines and corresponding timer IDs:\n"; + dump += addLinePrefix(dumpMap(mDeadlines, constToString, + [](const Deadline& deadline) { + return std::to_string(deadline.timerId); + }), + " ") + + "\n"; + return dump; +} + +void TimerProvider::triggerCallbacks(nsecs_t when) { + while (!mDeadlines.empty() && when >= mDeadlines.begin()->first) { + const auto& deadlinePair = mDeadlines.begin(); + deadlinePair->second.callback(when); + mDeadlines.erase(deadlinePair); + } + requestTimeout(); +} + +GesturesTimer* TimerProvider::createTimer() { + mTimers.push_back(std::make_unique<GesturesTimer>()); + mTimers.back()->id = mNextTimerId; + mNextTimerId++; + return mTimers.back().get(); +} + +void TimerProvider::setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback, + void* callbackData) { + setDeadlineWithoutRequestingTimeout(timer, delay, callback, callbackData); + requestTimeout(); +} + +void TimerProvider::setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay, + GesturesTimerCallback callback, + void* callbackData) { + const nsecs_t now = getCurrentTime(); + const nsecs_t time = now + delay; + std::function<void(nsecs_t)> wrappedCallback = [=, this](nsecs_t triggerTime) { + stime_t nextDelay = callback(nsecsToStime(triggerTime), callbackData); + if (nextDelay >= 0.0) { + // When rescheduling a deadline, we know that we're running inside a call to + // triggerCallbacks, at the end of which requestTimeout will be called. This means that + // we don't want to call the public setDeadline, as that will request a timeout before + // triggerCallbacks has removed this current deadline, resulting in a request for a + // timeout that has already passed. + setDeadlineWithoutRequestingTimeout(timer, stimeToNsecs(nextDelay), callback, + callbackData); + } + }; + mDeadlines.insert({time, Deadline(wrappedCallback, timer->id)}); +} + +void TimerProvider::cancelTimer(GesturesTimer* timer) { + int id = timer->id; + std::erase_if(mDeadlines, [id](const auto& item) { return item.second.timerId == id; }); + requestTimeout(); +} + +void TimerProvider::freeTimer(GesturesTimer* timer) { + cancelTimer(timer); + std::erase_if(mTimers, [timer](std::unique_ptr<GesturesTimer>& t) { return t.get() == timer; }); +} + +void TimerProvider::requestTimeout() { + if (!mDeadlines.empty()) { + // Because a std::multimap is sorted by key, we simply use the time for the first entry. + mReaderContext.requestTimeoutAtTime(mDeadlines.begin()->first); + } +} + +nsecs_t TimerProvider::getCurrentTime() { + return systemTime(SYSTEM_TIME_MONOTONIC); +} + +} // namespace android
\ No newline at end of file diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.h b/services/inputflinger/reader/mapper/gestures/TimerProvider.h new file mode 100644 index 0000000000..7c870e0a0f --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.h @@ -0,0 +1,88 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <functional> +#include <list> +#include <map> +#include <memory> +#include <vector> + +#include <utils/Timers.h> + +#include "InputReaderContext.h" +#include "NotifyArgs.h" +#include "include/gestures.h" + +namespace android { + +extern const GesturesTimerProvider kGestureTimerProvider; + +// Implementation of a gestures library timer provider, which allows the library to set and cancel +// callbacks. +class TimerProvider { +public: + TimerProvider(InputReaderContext& context); + virtual ~TimerProvider() = default; + + // Disable copy and move, since pointers to TimerProvider objects are used in callbacks. + TimerProvider(const TimerProvider&) = delete; + TimerProvider& operator=(const TimerProvider&) = delete; + + std::string dump(); + void triggerCallbacks(nsecs_t when); + + // Methods to be called by the gestures library: + GesturesTimer* createTimer(); + void setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback, + void* callbackData); + void cancelTimer(GesturesTimer* timer); + void freeTimer(GesturesTimer* timer); + +protected: + // A wrapper for the system clock, to allow tests to override it. + virtual nsecs_t getCurrentTime(); + +private: + void setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay, + GesturesTimerCallback callback, void* callbackData); + // Requests a timeout from the InputReader for the nearest deadline in mDeadlines. Must be + // called whenever mDeadlines is modified. + void requestTimeout(); + + InputReaderContext& mReaderContext; + int mNextTimerId = 0; + std::vector<std::unique_ptr<GesturesTimer>> mTimers; + + struct Deadline { + Deadline(std::function<void(nsecs_t)> callback, int timerId) + : callback(callback), timerId(timerId) {} + const std::function<void(nsecs_t)> callback; + const int timerId; + }; + + std::multimap<nsecs_t /*time*/, Deadline> mDeadlines; +}; + +} // namespace android + +// Represents a "timer" registered by the gestures library. In practice, this just means a set of +// deadlines that can be cancelled as a group. The library's API requires this to be in the +// top-level namespace. +struct GesturesTimer { + int id = -1; +};
\ No newline at end of file diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 64100467db..d87a5a7337 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -63,6 +63,7 @@ cc_test { "PropertyProvider_test.cpp", "SlopController_test.cpp", "SyncQueue_test.cpp", + "TimerProvider_test.cpp", "TestInputListener.cpp", "TestInputListenerMatchers.cpp", "TouchpadInputMapper_test.cpp", diff --git a/services/inputflinger/tests/TimerProvider_test.cpp b/services/inputflinger/tests/TimerProvider_test.cpp new file mode 100644 index 0000000000..cb28823679 --- /dev/null +++ b/services/inputflinger/tests/TimerProvider_test.cpp @@ -0,0 +1,311 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gestures/TimerProvider.h" + +#include <vector> + +#include <gtest/gtest.h> + +#include "InterfaceMocks.h" +#include "TestConstants.h" +#include "include/gestures.h" + +namespace android { + +namespace { + +class TestTimerProvider : public TimerProvider { +public: + TestTimerProvider(InputReaderContext& context) : TimerProvider(context) {} + + void setCurrentTime(nsecs_t time) { mCurrentTime = time; } + +protected: + nsecs_t getCurrentTime() override { return mCurrentTime; } + +private: + nsecs_t mCurrentTime = 0; +}; + +stime_t pushTimeOntoVector(stime_t triggerTime, void* data) { + std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(data); + times->push_back(triggerTime); + return NO_DEADLINE; +} + +stime_t copyTimeToVariable(stime_t triggerTime, void* data) { + stime_t* time = static_cast<stime_t*>(data); + *time = triggerTime; + return NO_DEADLINE; +} + +stime_t incrementInt(stime_t triggerTime, void* data) { + int* count = static_cast<int*>(data); + *count += 1; + return NO_DEADLINE; +} + +} // namespace + +using testing::AtLeast; + +class TimerProviderTest : public testing::Test { +public: + TimerProviderTest() : mProvider(mMockContext) {} + +protected: + void triggerCallbacksWithFakeTime(nsecs_t time) { + mProvider.setCurrentTime(time); + mProvider.triggerCallbacks(time); + } + + MockInputReaderContext mMockContext; + TestTimerProvider mProvider; +}; + +TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsExactlyOnTime) { + GesturesTimer* timer = mProvider.createTimer(); + std::vector<stime_t> callTimes; + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(3); + + // Call through kGestureTimerProvider in this test case, so that we cover the stime_t to nsecs_t + // conversion code. This is why the delay is 1.0 rather than 1'000'000'000 here. + kGestureTimerProvider.set_fn(&mProvider, timer, 1.0, &pushTimeOntoVector, &callTimes); + + triggerCallbacksWithFakeTime(900'000'000); + triggerCallbacksWithFakeTime(999'999'999); + EXPECT_EQ(0u, callTimes.size()); + triggerCallbacksWithFakeTime(1'000'000'000); + ASSERT_EQ(1u, callTimes.size()); + EXPECT_NEAR(1.0, callTimes[0], EPSILON); + + // Now that the timer has triggered, it shouldn't trigger again if we get another timeout from + // InputReader. + triggerCallbacksWithFakeTime(1'300'000'000); + EXPECT_EQ(1u, callTimes.size()); +} + +TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsLate) { + GesturesTimer* timer = mProvider.createTimer(); + stime_t callTime = -1.0; + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1); + mProvider.setDeadline(timer, 1'000'000'000, ©TimeToVariable, &callTime); + + triggerCallbacksWithFakeTime(1'010'000'000); + EXPECT_NEAR(1.01, callTime, EPSILON); +} + +TEST_F(TimerProviderTest, SingleRescheduledDeadlineTriggers) { + GesturesTimer* timer = mProvider.createTimer(); + std::vector<stime_t> callTimes; + auto callback = [](stime_t triggerTime, void* callbackData) { + std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(callbackData); + times->push_back(triggerTime); + if (times->size() < 2) { + return 1.0; + } else { + return NO_DEADLINE; + } + }; + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1); + // The deadline should be rescheduled for 2.01s, since the first triggerCallbacks call is 0.01s + // late. + EXPECT_CALL(mMockContext, requestTimeoutAtTime(2'010'000'000)).Times(1); + + mProvider.setDeadline(timer, 1'000'000'000, callback, &callTimes); + + triggerCallbacksWithFakeTime(1'010'000'000); + ASSERT_EQ(1u, callTimes.size()); + EXPECT_NEAR(1.01, callTimes[0], EPSILON); + + triggerCallbacksWithFakeTime(2'020'000'000); + ASSERT_EQ(2u, callTimes.size()); + EXPECT_NEAR(1.01, callTimes[0], EPSILON); + EXPECT_NEAR(2.02, callTimes[1], EPSILON); + + triggerCallbacksWithFakeTime(3'000'000'000); + EXPECT_EQ(2u, callTimes.size()); +} + +TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithMultipleTimeouts) { + GesturesTimer* timer = mProvider.createTimer(); + std::vector<stime_t> callTimes1; + std::vector<stime_t> callTimes2; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1)); + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1); + + mProvider.setDeadline(timer, 1'000'000'000, &pushTimeOntoVector, &callTimes1); + mProvider.setDeadline(timer, 1'500'000'000, &pushTimeOntoVector, &callTimes2); + + EXPECT_EQ(0u, callTimes1.size()); + EXPECT_EQ(0u, callTimes2.size()); + + triggerCallbacksWithFakeTime(1'010'000'000); + ASSERT_EQ(1u, callTimes1.size()); + EXPECT_NEAR(1.01, callTimes1[0], EPSILON); + EXPECT_EQ(0u, callTimes2.size()); + + triggerCallbacksWithFakeTime(1'500'000'000); + EXPECT_EQ(1u, callTimes1.size()); + ASSERT_EQ(1u, callTimes2.size()); + EXPECT_NEAR(1.5, callTimes2[0], EPSILON); +} + +TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithOneLateTimeout) { + GesturesTimer* timer = mProvider.createTimer(); + stime_t callTime1 = -1.0; + stime_t callTime2 = -1.0; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1)); + + mProvider.setDeadline(timer, 1'000'000'000, ©TimeToVariable, &callTime1); + mProvider.setDeadline(timer, 1'500'000'000, ©TimeToVariable, &callTime2); + + triggerCallbacksWithFakeTime(1'510'000'000); + EXPECT_NEAR(1.51, callTime1, EPSILON); + EXPECT_NEAR(1.51, callTime2, EPSILON); +} + +TEST_F(TimerProviderTest, MultipleDeadlinesAtSameTimeTriggerTogether) { + GesturesTimer* timer = mProvider.createTimer(); + stime_t callTime1 = -1.0; + stime_t callTime2 = -1.0; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1)); + + mProvider.setDeadline(timer, 1'000'000'000, ©TimeToVariable, &callTime1); + mProvider.setDeadline(timer, 1'000'000'000, ©TimeToVariable, &callTime2); + + triggerCallbacksWithFakeTime(1'000'000'000); + EXPECT_NEAR(1.0, callTime1, EPSILON); + EXPECT_NEAR(1.0, callTime2, EPSILON); +} + +TEST_F(TimerProviderTest, MultipleTimersTriggerCorrectly) { + GesturesTimer* timer1 = mProvider.createTimer(); + GesturesTimer* timer2 = mProvider.createTimer(); + std::vector<stime_t> callTimes1; + std::vector<stime_t> callTimes2; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1)); + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'250'000'000)).Times(1); + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1); + + mProvider.setDeadline(timer1, 500'000'000, &pushTimeOntoVector, &callTimes1); + mProvider.setDeadline(timer1, 1'250'000'000, &pushTimeOntoVector, &callTimes1); + mProvider.setDeadline(timer1, 1'500'000'000, &pushTimeOntoVector, &callTimes1); + mProvider.setDeadline(timer2, 750'000'000, &pushTimeOntoVector, &callTimes2); + mProvider.setDeadline(timer2, 1'250'000'000, &pushTimeOntoVector, &callTimes2); + + triggerCallbacksWithFakeTime(800'000'000); + ASSERT_EQ(1u, callTimes1.size()); + EXPECT_NEAR(0.8, callTimes1[0], EPSILON); + ASSERT_EQ(1u, callTimes2.size()); + EXPECT_NEAR(0.8, callTimes2[0], EPSILON); + + triggerCallbacksWithFakeTime(1'250'000'000); + ASSERT_EQ(2u, callTimes1.size()); + EXPECT_NEAR(1.25, callTimes1[1], EPSILON); + ASSERT_EQ(2u, callTimes2.size()); + EXPECT_NEAR(1.25, callTimes2[1], EPSILON); + + triggerCallbacksWithFakeTime(1'501'000'000); + ASSERT_EQ(3u, callTimes1.size()); + EXPECT_NEAR(1.501, callTimes1[2], EPSILON); + EXPECT_EQ(2u, callTimes2.size()); +} + +TEST_F(TimerProviderTest, CancelledTimerDoesntTrigger) { + GesturesTimer* timer = mProvider.createTimer(); + int numCalls = 0; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1)); + mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCalls); + mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls); + mProvider.cancelTimer(timer); + + triggerCallbacksWithFakeTime(1'100'000'000); + EXPECT_EQ(0, numCalls); +} + +TEST_F(TimerProviderTest, CancellingOneTimerDoesntAffectOthers) { + GesturesTimer* timer1 = mProvider.createTimer(); + GesturesTimer* timer2 = mProvider.createTimer(); + int numCalls1 = 0; + int numCalls2 = 0; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1)); + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1); + + mProvider.setDeadline(timer1, 500'000'000, &incrementInt, &numCalls1); + mProvider.setDeadline(timer2, 500'000'000, &incrementInt, &numCalls2); + mProvider.setDeadline(timer2, 1'000'000'000, &incrementInt, &numCalls2); + mProvider.cancelTimer(timer1); + + triggerCallbacksWithFakeTime(501'000'000); + EXPECT_EQ(0, numCalls1); + EXPECT_EQ(1, numCalls2); + + triggerCallbacksWithFakeTime(1'000'000'000); + EXPECT_EQ(0, numCalls1); + EXPECT_EQ(2, numCalls2); +} + +TEST_F(TimerProviderTest, CancellingOneTimerCausesNewTimeoutRequestForAnother) { + GesturesTimer* timer1 = mProvider.createTimer(); + GesturesTimer* timer2 = mProvider.createTimer(); + auto callback = [](stime_t, void*) { return NO_DEADLINE; }; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1)); + + mProvider.setDeadline(timer1, 500'000'000, callback, nullptr); + mProvider.setDeadline(timer2, 1'000'000'000, callback, nullptr); + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1); + mProvider.cancelTimer(timer1); +} + +TEST_F(TimerProviderTest, CancelledTimerCanBeReused) { + GesturesTimer* timer = mProvider.createTimer(); + int numCallsBeforeCancellation = 0; + int numCallsAfterCancellation = 0; + + EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(1); + EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1); + + mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCallsBeforeCancellation); + mProvider.cancelTimer(timer); + mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCallsAfterCancellation); + + triggerCallbacksWithFakeTime(1'000'000'000); + EXPECT_EQ(0, numCallsBeforeCancellation); + EXPECT_EQ(1, numCallsAfterCancellation); +} + +TEST_F(TimerProviderTest, FreeingTimerCancelsFirst) { + GesturesTimer* timer = mProvider.createTimer(); + int numCalls = 0; + + mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls); + mProvider.freeTimer(timer); + + triggerCallbacksWithFakeTime(1'000'000'000); + EXPECT_EQ(0, numCalls); +} + +} // namespace android
\ No newline at end of file |