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
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 0e3fbb1..63c0e40 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -75,11 +75,12 @@
}
/**
- * 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 @@
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 2af5a4a..a0563f9 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -20,3 +20,10 @@
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 8fe6411..e1806a0 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -69,6 +69,7 @@
"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 0aea0b3..5766b14 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -946,6 +946,7 @@
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 6ea004d..3e5e01e 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 @@
: 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 @@
// 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 @@
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::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 @@
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 @@
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 @@
// 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 47d712e..a68ae43 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 @@
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 @@
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 @@
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 0000000..df2f260
--- /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 0000000..7c870e0
--- /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 6410046..d87a5a7 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -63,6 +63,7 @@
"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 0000000..cb28823
--- /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