|
| 1 | +/** |
| 2 | + * Copyright 2024 The Android Open Source Project |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +#include <input/InputConsumerNoResampling.h> |
| 18 | + |
| 19 | +#include <chrono> |
| 20 | +#include <iostream> |
| 21 | +#include <memory> |
| 22 | +#include <queue> |
| 23 | + |
| 24 | +#include <TestEventMatchers.h> |
| 25 | +#include <TestInputChannel.h> |
| 26 | +#include <android-base/logging.h> |
| 27 | +#include <gmock/gmock.h> |
| 28 | +#include <gtest/gtest.h> |
| 29 | +#include <input/Input.h> |
| 30 | +#include <input/InputEventBuilders.h> |
| 31 | +#include <input/Resampler.h> |
| 32 | +#include <utils/Looper.h> |
| 33 | +#include <utils/StrongPointer.h> |
| 34 | + |
| 35 | +namespace android { |
| 36 | +namespace { |
| 37 | + |
| 38 | +using std::chrono::nanoseconds; |
| 39 | + |
| 40 | +using ::testing::AllOf; |
| 41 | +using ::testing::Matcher; |
| 42 | + |
| 43 | +const int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; |
| 44 | +const int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; |
| 45 | + |
| 46 | +struct Pointer { |
| 47 | + int32_t id{0}; |
| 48 | + ToolType toolType{ToolType::FINGER}; |
| 49 | + float x{0.0f}; |
| 50 | + float y{0.0f}; |
| 51 | + bool isResampled{false}; |
| 52 | + |
| 53 | + PointerBuilder asPointerBuilder() const { |
| 54 | + return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled); |
| 55 | + } |
| 56 | +}; |
| 57 | + |
| 58 | +} // namespace |
| 59 | + |
| 60 | +class InputConsumerFilteredResamplingTest : public ::testing::Test, public InputConsumerCallbacks { |
| 61 | +protected: |
| 62 | + InputConsumerFilteredResamplingTest() |
| 63 | + : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")}, |
| 64 | + mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} { |
| 65 | + Looper::setForThread(mLooper); |
| 66 | + mConsumer = std::make_unique< |
| 67 | + InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, []() { |
| 68 | + return std::make_unique<FilteredLegacyResampler>(/*minCutoffFreq=*/4.7, /*beta=*/0.01); |
| 69 | + }); |
| 70 | + } |
| 71 | + |
| 72 | + void invokeLooperCallback() const { |
| 73 | + sp<LooperCallback> callback; |
| 74 | + ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr, |
| 75 | + /*events=*/nullptr, &callback, /*data=*/nullptr)); |
| 76 | + ASSERT_NE(callback, nullptr); |
| 77 | + callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr); |
| 78 | + } |
| 79 | + |
| 80 | + void assertOnBatchedInputEventPendingWasCalled() { |
| 81 | + ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL) |
| 82 | + << "onBatchedInputEventPending was not called"; |
| 83 | + --mOnBatchedInputEventPendingInvocationCount; |
| 84 | + } |
| 85 | + |
| 86 | + void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) { |
| 87 | + ASSERT_TRUE(!mMotionEvents.empty()) << "No motion events were received"; |
| 88 | + std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front()); |
| 89 | + mMotionEvents.pop(); |
| 90 | + ASSERT_NE(motionEvent, nullptr) << "The consumed motion event must not be nullptr"; |
| 91 | + EXPECT_THAT(*motionEvent, matcher); |
| 92 | + } |
| 93 | + |
| 94 | + InputMessage nextPointerMessage(nanoseconds eventTime, int32_t action, const Pointer& pointer); |
| 95 | + |
| 96 | + std::shared_ptr<TestInputChannel> mClientTestChannel; |
| 97 | + sp<Looper> mLooper; |
| 98 | + std::unique_ptr<InputConsumerNoResampling> mConsumer; |
| 99 | + |
| 100 | + // Batched input events |
| 101 | + std::queue<std::unique_ptr<KeyEvent>> mKeyEvents; |
| 102 | + std::queue<std::unique_ptr<MotionEvent>> mMotionEvents; |
| 103 | + std::queue<std::unique_ptr<FocusEvent>> mFocusEvents; |
| 104 | + std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents; |
| 105 | + std::queue<std::unique_ptr<DragEvent>> mDragEvents; |
| 106 | + std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents; |
| 107 | + |
| 108 | +private: |
| 109 | + // InputConsumer callbacks |
| 110 | + void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override { |
| 111 | + mKeyEvents.push(std::move(event)); |
| 112 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 113 | + } |
| 114 | + |
| 115 | + void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override { |
| 116 | + mMotionEvents.push(std::move(event)); |
| 117 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 118 | + } |
| 119 | + |
| 120 | + void onBatchedInputEventPending(int32_t pendingBatchSource) override { |
| 121 | + if (!mConsumer->probablyHasInput()) { |
| 122 | + ADD_FAILURE() << "Should deterministically have input because there is a batch"; |
| 123 | + } |
| 124 | + ++mOnBatchedInputEventPendingInvocationCount; |
| 125 | + } |
| 126 | + |
| 127 | + void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override { |
| 128 | + mFocusEvents.push(std::move(event)); |
| 129 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 130 | + } |
| 131 | + |
| 132 | + void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override { |
| 133 | + mCaptureEvents.push(std::move(event)); |
| 134 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 135 | + } |
| 136 | + |
| 137 | + void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override { |
| 138 | + mDragEvents.push(std::move(event)); |
| 139 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 140 | + } |
| 141 | + |
| 142 | + void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override { |
| 143 | + mTouchModeEvents.push(std::move(event)); |
| 144 | + mConsumer->finishInputEvent(seq, /*handled=*/true); |
| 145 | + } |
| 146 | + |
| 147 | + uint32_t mLastSeq{0}; |
| 148 | + size_t mOnBatchedInputEventPendingInvocationCount{0}; |
| 149 | +}; |
| 150 | + |
| 151 | +InputMessage InputConsumerFilteredResamplingTest::nextPointerMessage(nanoseconds eventTime, |
| 152 | + int32_t action, |
| 153 | + const Pointer& pointer) { |
| 154 | + ++mLastSeq; |
| 155 | + return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq} |
| 156 | + .eventTime(eventTime.count()) |
| 157 | + .source(AINPUT_SOURCE_TOUCHSCREEN) |
| 158 | + .action(action) |
| 159 | + .pointer(pointer.asPointerBuilder()) |
| 160 | + .build(); |
| 161 | +} |
| 162 | + |
| 163 | +TEST_F(InputConsumerFilteredResamplingTest, NeighboringTimestampsDoNotResultInZeroDivision) { |
| 164 | + mClientTestChannel->enqueueMessage( |
| 165 | + nextPointerMessage(0ms, ACTION_DOWN, Pointer{.x = 0.0f, .y = 0.0f})); |
| 166 | + |
| 167 | + invokeLooperCallback(); |
| 168 | + |
| 169 | + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithSampleCount(1))); |
| 170 | + |
| 171 | + const std::chrono::nanoseconds initialTime{56'821'700'000'000}; |
| 172 | + |
| 173 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 4'929'000ns, ACTION_MOVE, |
| 174 | + Pointer{.x = 1.0f, .y = 1.0f})); |
| 175 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 9'352'000ns, ACTION_MOVE, |
| 176 | + Pointer{.x = 2.0f, .y = 2.0f})); |
| 177 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 14'531'000ns, ACTION_MOVE, |
| 178 | + Pointer{.x = 3.0f, .y = 3.0f})); |
| 179 | + |
| 180 | + invokeLooperCallback(); |
| 181 | + mConsumer->consumeBatchedInputEvents(initialTime.count() + 18'849'395 /*ns*/); |
| 182 | + |
| 183 | + assertOnBatchedInputEventPendingWasCalled(); |
| 184 | + // Three samples are expected. The first two of the batch, and the resampled one. The |
| 185 | + // coordinates of the resampled sample are hardcoded because the matcher requires them. However, |
| 186 | + // the primary intention here is to check that the last sample is resampled. |
| 187 | + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(3), |
| 188 | + WithSample(/*sampleIndex=*/2, |
| 189 | + Sample{initialTime + 13'849'395ns, |
| 190 | + {PointerArgs{.x = 1.3286f, |
| 191 | + .y = 1.3286f, |
| 192 | + .isResampled = true}}}))); |
| 193 | + |
| 194 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 20'363'000ns, ACTION_MOVE, |
| 195 | + Pointer{.x = 4.0f, .y = 4.0f})); |
| 196 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 25'745'000ns, ACTION_MOVE, |
| 197 | + Pointer{.x = 5.0f, .y = 5.0f})); |
| 198 | + // This sample is part of the stream of messages, but should not be consumed because its |
| 199 | + // timestamp is greater than the ajusted frame time. |
| 200 | + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 31'337'000ns, ACTION_MOVE, |
| 201 | + Pointer{.x = 6.0f, .y = 6.0f})); |
| 202 | + |
| 203 | + invokeLooperCallback(); |
| 204 | + mConsumer->consumeBatchedInputEvents(initialTime.count() + 35'516'062 /*ns*/); |
| 205 | + |
| 206 | + assertOnBatchedInputEventPendingWasCalled(); |
| 207 | + // Four samples are expected because the last sample of the previous batch was not consumed. |
| 208 | + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(4))); |
| 209 | + |
| 210 | + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); |
| 211 | + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); |
| 212 | + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); |
| 213 | + mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); |
| 214 | + mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true); |
| 215 | + mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true); |
| 216 | +} |
| 217 | + |
| 218 | +} // namespace android |
0 commit comments