diff options
-rw-r--r-- | include/input/InputTransport.h | 2 | ||||
-rw-r--r-- | include/input/InputVerifier.h | 49 | ||||
-rw-r--r-- | libs/input/Android.bp | 1 | ||||
-rw-r--r-- | libs/input/InputTransport.cpp | 15 | ||||
-rw-r--r-- | libs/input/InputVerifier.cpp | 128 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 24 |
6 files changed, 214 insertions, 5 deletions
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 1c52792cf6..a1be542d7b 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -38,6 +38,7 @@ #include <binder/IBinder.h> #include <binder/Parcelable.h> #include <input/Input.h> +#include <input/InputVerifier.h> #include <sys/stat.h> #include <ui/Transform.h> #include <utils/BitSet.h> @@ -444,6 +445,7 @@ public: private: std::shared_ptr<InputChannel> mChannel; + InputVerifier mInputVerifier; }; /* diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h new file mode 100644 index 0000000000..d4589f53b5 --- /dev/null +++ b/include/input/InputVerifier.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 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 <input/Input.h> +#include <map> + +namespace android { + +/* + * Crash if the provided touch stream is inconsistent. + * + * TODO(b/211379801): Add support for hover events: + * - No hover move without enter + * - No touching pointers when hover enter + * - No hovering pointers when touching + * - Only 1 hovering pointer max + */ +class InputVerifier { +public: + InputVerifier(const std::string& name); + + void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags); + +private: + const std::string mName; + std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice; + void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const; +}; + +} // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 3809b6dd81..48cb72cfb7 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -47,6 +47,7 @@ cc_library { "Input.cpp", "InputDevice.cpp", "InputEventLabels.cpp", + "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", "KeyLayoutMap.cpp", diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 9f0a314041..d1cd50ccff 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -76,6 +76,14 @@ static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; */ static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; +/** + * Crash if the events that are getting sent to the InputPublisher are inconsistent. + * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG" + */ +static bool verifyEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO); +} + template<typename T> inline static T min(const T& a, const T& b) { return a < b ? a : b; @@ -492,7 +500,8 @@ base::unique_fd InputChannel::dupFd() const { // --- InputPublisher --- -InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) : mChannel(channel) {} +InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) + : mChannel(channel), mInputVerifier(channel->getName()) {} InputPublisher::~InputPublisher() { } @@ -555,6 +564,10 @@ status_t InputPublisher::publishMotionEvent( mChannel->getName().c_str(), action); ATRACE_NAME(message.c_str()); } + if (verifyEvents()) { + mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties, + pointerCoords, flags); + } if (DEBUG_TRANSPORT_ACTIONS) { std::string transformString; transform.dump(transformString, "transform", " "); diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp new file mode 100644 index 0000000000..eb758045cc --- /dev/null +++ b/libs/input/InputVerifier.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "InputVerifier" + +#include <android-base/logging.h> +#include <input/InputVerifier.h> + +namespace android { + +/** + * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead + * to inconsistent events. + * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG" + */ +static bool logEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO); +} + +// --- InputVerifier --- + +InputVerifier::InputVerifier(const std::string& name) : mName(name){}; + +void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags) { + if (logEvents()) { + LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device " + << deviceId << " (" << pointerCount << " pointer" + << (pointerCount == 1 ? "" : "s") << ") on " << mName; + } + + switch (MotionEvent::getActionMasked(action)) { + case AMOTION_EVENT_ACTION_DOWN: { + auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}}); + if (!inserted) { + LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second + << " for device " << deviceId << " on " << mName; + } + it->second.set(pointerProperties[0].id); + break; + } + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_MOVE: { + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE"); + break; + } + case AMOTION_EVENT_ACTION_POINTER_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on " + << mName; + } + const auto& [_, touchingPointerIds] = *it; + if (touchingPointerIds.count() != 1) { + LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + const int32_t pointerId = pointerProperties[0].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId + << " is not touching. Touching pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + mTouchingPointerIdsByDevice.erase(it); + break; + } + case AMOTION_EVENT_ACTION_CANCEL: { + if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) { + LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED"; + } + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL"); + mTouchingPointerIdsByDevice.erase(deviceId); + break; + } + } +} + +void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId + << " on " << mName; + } + const auto& [_, touchingPointerIds] = *it; + for (size_t i = 0; i < pointerCount; i++) { + const int32_t pointerId = pointerProperties[i].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got " << action << " for pointerId " << pointerId + << " but the touching pointers are " << touchingPointerIds << " on " + << mName; + } + } +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index bd9038d42e..711366dd5d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2052,8 +2052,17 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 1, + AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build(), + INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); @@ -3007,8 +3016,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { window1->assertNoEvents(); // Now move the pointer on the first window - mDispatcher->notifyMotion( - &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}}))); + mDispatcher->notifyMotion(&( + args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}))); mDispatcher->waitForIdle(); window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); @@ -5465,6 +5474,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID); monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID); + // Lift up the touch from the second display + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID); + monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID); + // Test inject a non-pointer motion event. // If specific a display, it will dispatch to the focused window of particular display, // or it will dispatch to the focused window of focused display. |