Loading services/inputflinger/dispatcher/InputDispatcher.cpp +38 −8 Original line number Diff line number Diff line Loading @@ -1687,15 +1687,11 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, newTouchedWindowHandle = nullptr; } // Ensure the window has a connection and the connection is responsive if (newTouchedWindowHandle != nullptr) { sp<Connection> connection = getConnectionLocked(newTouchedWindowHandle->getToken()); if (connection == nullptr) { ALOGI("Could not find connection for %s", newTouchedWindowHandle->getName().c_str()); newTouchedWindowHandle = nullptr; } else if (!connection->responsive) { // don't send the new touch to an unresponsive window ALOGW("Unresponsive window %s will not get the new gesture at %" PRIu64, const bool isResponsive = hasResponsiveConnectionLocked(*newTouchedWindowHandle); if (!isResponsive) { ALOGW("%s will not receive the new gesture at %" PRIu64, newTouchedWindowHandle->getName().c_str(), entry.eventTime); newTouchedWindowHandle = nullptr; } Loading Loading @@ -3678,6 +3674,29 @@ bool InputDispatcher::hasWindowHandleLocked(const sp<InputWindowHandle>& windowH return false; } bool InputDispatcher::hasResponsiveConnectionLocked(InputWindowHandle& windowHandle) const { sp<Connection> connection = getConnectionLocked(windowHandle.getToken()); const bool noInputChannel = windowHandle.getInfo()->inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL); if (connection != nullptr && noInputChannel) { ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s", windowHandle.getName().c_str(), connection->inputChannel->getName().c_str()); return false; } if (connection == nullptr) { if (!noInputChannel) { ALOGI("Could not find connection for %s", windowHandle.getName().c_str()); } return false; } if (!connection->responsive) { ALOGW("Window %s is not responsive", windowHandle.getName().c_str()); return false; } return true; } std::shared_ptr<InputChannel> InputDispatcher::getInputChannelLocked( const sp<IBinder>& token) const { size_t count = mInputChannelsByToken.count(token); Loading Loading @@ -3773,6 +3792,17 @@ void InputDispatcher::setInputWindowsLocked( ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str()); } // Ensure all tokens are null if the window has feature NO_INPUT_CHANNEL for (const sp<InputWindowHandle>& window : inputWindowHandles) { const bool noInputWindow = window->getInfo()->inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL); if (noInputWindow && window->getToken() != nullptr) { ALOGE("%s has feature NO_INPUT_WINDOW, but a non-null token. Clearing", window->getName().c_str()); window->releaseChannel(); } } // Copy old handles for release if they are no longer present. const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId); Loading services/inputflinger/dispatcher/InputDispatcher.h +1 −0 Original line number Diff line number Diff line Loading @@ -307,6 +307,7 @@ private: std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const REQUIRES(mLock); bool hasWindowHandleLocked(const sp<InputWindowHandle>& windowHandle) const REQUIRES(mLock); bool hasResponsiveConnectionLocked(InputWindowHandle& windowHandle) const REQUIRES(mLock); /* * Validate and update InputWindowHandles for a given display. Loading services/inputflinger/tests/InputDispatcher_test.cpp +84 −5 Original line number Diff line number Diff line Loading @@ -749,9 +749,9 @@ public: FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, const sp<InputDispatcher>& dispatcher, const std::string name, int32_t displayId, sp<IBinder> token = nullptr) int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt) : mName(name) { if (token == nullptr) { if (token == std::nullopt) { std::unique_ptr<InputChannel> serverChannel, clientChannel; InputChannel::openInputChannelPair(name, serverChannel, clientChannel); mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(clientChannel), name); Loading @@ -762,7 +762,7 @@ public: inputApplicationHandle->updateInfo(); mInfo.applicationInfo = *inputApplicationHandle->getInfo(); mInfo.token = token; mInfo.token = *token; mInfo.id = sId++; mInfo.name = name; mInfo.type = InputWindowInfo::Type::APPLICATION; Loading Loading @@ -807,6 +807,8 @@ public: void setFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags = flags; } void setInputFeatures(InputWindowInfo::Feature features) { mInfo.inputFeatures = features; } void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) { mInfo.transform.set(dsdx, dtdx, dtdy, dsdy); } Loading Loading @@ -894,8 +896,12 @@ public: } void assertNoEvents() { ASSERT_NE(mInputReceiver, nullptr) << "Call 'assertNoEvents' on a window with an InputReceiver"; if (mInputReceiver == nullptr && mInfo.inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL)) { return; // Can't receive events if the window does not have input channel } ASSERT_NE(nullptr, mInputReceiver) << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL"; mInputReceiver->assertNoEvents(); } Loading Loading @@ -3307,4 +3313,77 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { mFocusedWindow->assertNoEvents(); } // These tests ensure we cannot send touch events to a window that's positioned behind a window // that has feature NO_INPUT_CHANNEL. // Layout: // Top (closest to user) // mNoInputWindow (above all windows) // mBottomWindow // Bottom (furthest from user) class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); mApplication = std::make_shared<FakeApplicationHandle>(); mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, "Window without input channel", ADISPLAY_ID_DEFAULT, std::make_optional<sp<IBinder>>(nullptr) /*token*/); mNoInputWindow->setInputFeatures(InputWindowInfo::Feature::NO_INPUT_CHANNEL); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel mBottomWindow = new FakeWindowHandle(mApplication, mDispatcher, "Bottom window", ADISPLAY_ID_DEFAULT); mBottomWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}}); } protected: std::shared_ptr<FakeApplicationHandle> mApplication; sp<FakeWindowHandle> mNoInputWindow; sp<FakeWindowHandle> mBottomWindow; }; TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouches) { PointF touchedPoint = {10, 10}; NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchedPoint}); mDispatcher->notifyMotion(&motionArgs); mNoInputWindow->assertNoEvents(); // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have // an input channel, it is not marked as FLAG_NOT_TOUCHABLE, // and therefore should prevent mBottomWindow from receiving touches mBottomWindow->assertNoEvents(); } /** * If a window has feature NO_INPUT_CHANNEL, and somehow (by mistake) still has an input channel, * ensure that this window does not receive any touches, and blocks touches to windows underneath. */ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouchesWithValidChannel) { mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, "Window with input channel and NO_INPUT_CHANNEL", ADISPLAY_ID_DEFAULT); mNoInputWindow->setInputFeatures(InputWindowInfo::Feature::NO_INPUT_CHANNEL); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}}); PointF touchedPoint = {10, 10}; NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchedPoint}); mDispatcher->notifyMotion(&motionArgs); mNoInputWindow->assertNoEvents(); mBottomWindow->assertNoEvents(); } } // namespace android::inputdispatcher Loading
services/inputflinger/dispatcher/InputDispatcher.cpp +38 −8 Original line number Diff line number Diff line Loading @@ -1687,15 +1687,11 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, newTouchedWindowHandle = nullptr; } // Ensure the window has a connection and the connection is responsive if (newTouchedWindowHandle != nullptr) { sp<Connection> connection = getConnectionLocked(newTouchedWindowHandle->getToken()); if (connection == nullptr) { ALOGI("Could not find connection for %s", newTouchedWindowHandle->getName().c_str()); newTouchedWindowHandle = nullptr; } else if (!connection->responsive) { // don't send the new touch to an unresponsive window ALOGW("Unresponsive window %s will not get the new gesture at %" PRIu64, const bool isResponsive = hasResponsiveConnectionLocked(*newTouchedWindowHandle); if (!isResponsive) { ALOGW("%s will not receive the new gesture at %" PRIu64, newTouchedWindowHandle->getName().c_str(), entry.eventTime); newTouchedWindowHandle = nullptr; } Loading Loading @@ -3678,6 +3674,29 @@ bool InputDispatcher::hasWindowHandleLocked(const sp<InputWindowHandle>& windowH return false; } bool InputDispatcher::hasResponsiveConnectionLocked(InputWindowHandle& windowHandle) const { sp<Connection> connection = getConnectionLocked(windowHandle.getToken()); const bool noInputChannel = windowHandle.getInfo()->inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL); if (connection != nullptr && noInputChannel) { ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s", windowHandle.getName().c_str(), connection->inputChannel->getName().c_str()); return false; } if (connection == nullptr) { if (!noInputChannel) { ALOGI("Could not find connection for %s", windowHandle.getName().c_str()); } return false; } if (!connection->responsive) { ALOGW("Window %s is not responsive", windowHandle.getName().c_str()); return false; } return true; } std::shared_ptr<InputChannel> InputDispatcher::getInputChannelLocked( const sp<IBinder>& token) const { size_t count = mInputChannelsByToken.count(token); Loading Loading @@ -3773,6 +3792,17 @@ void InputDispatcher::setInputWindowsLocked( ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str()); } // Ensure all tokens are null if the window has feature NO_INPUT_CHANNEL for (const sp<InputWindowHandle>& window : inputWindowHandles) { const bool noInputWindow = window->getInfo()->inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL); if (noInputWindow && window->getToken() != nullptr) { ALOGE("%s has feature NO_INPUT_WINDOW, but a non-null token. Clearing", window->getName().c_str()); window->releaseChannel(); } } // Copy old handles for release if they are no longer present. const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId); Loading
services/inputflinger/dispatcher/InputDispatcher.h +1 −0 Original line number Diff line number Diff line Loading @@ -307,6 +307,7 @@ private: std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const REQUIRES(mLock); bool hasWindowHandleLocked(const sp<InputWindowHandle>& windowHandle) const REQUIRES(mLock); bool hasResponsiveConnectionLocked(InputWindowHandle& windowHandle) const REQUIRES(mLock); /* * Validate and update InputWindowHandles for a given display. Loading
services/inputflinger/tests/InputDispatcher_test.cpp +84 −5 Original line number Diff line number Diff line Loading @@ -749,9 +749,9 @@ public: FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle, const sp<InputDispatcher>& dispatcher, const std::string name, int32_t displayId, sp<IBinder> token = nullptr) int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt) : mName(name) { if (token == nullptr) { if (token == std::nullopt) { std::unique_ptr<InputChannel> serverChannel, clientChannel; InputChannel::openInputChannelPair(name, serverChannel, clientChannel); mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(clientChannel), name); Loading @@ -762,7 +762,7 @@ public: inputApplicationHandle->updateInfo(); mInfo.applicationInfo = *inputApplicationHandle->getInfo(); mInfo.token = token; mInfo.token = *token; mInfo.id = sId++; mInfo.name = name; mInfo.type = InputWindowInfo::Type::APPLICATION; Loading Loading @@ -807,6 +807,8 @@ public: void setFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags = flags; } void setInputFeatures(InputWindowInfo::Feature features) { mInfo.inputFeatures = features; } void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) { mInfo.transform.set(dsdx, dtdx, dtdy, dsdy); } Loading Loading @@ -894,8 +896,12 @@ public: } void assertNoEvents() { ASSERT_NE(mInputReceiver, nullptr) << "Call 'assertNoEvents' on a window with an InputReceiver"; if (mInputReceiver == nullptr && mInfo.inputFeatures.test(InputWindowInfo::Feature::NO_INPUT_CHANNEL)) { return; // Can't receive events if the window does not have input channel } ASSERT_NE(nullptr, mInputReceiver) << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL"; mInputReceiver->assertNoEvents(); } Loading Loading @@ -3307,4 +3313,77 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { mFocusedWindow->assertNoEvents(); } // These tests ensure we cannot send touch events to a window that's positioned behind a window // that has feature NO_INPUT_CHANNEL. // Layout: // Top (closest to user) // mNoInputWindow (above all windows) // mBottomWindow // Bottom (furthest from user) class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); mApplication = std::make_shared<FakeApplicationHandle>(); mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, "Window without input channel", ADISPLAY_ID_DEFAULT, std::make_optional<sp<IBinder>>(nullptr) /*token*/); mNoInputWindow->setInputFeatures(InputWindowInfo::Feature::NO_INPUT_CHANNEL); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel mBottomWindow = new FakeWindowHandle(mApplication, mDispatcher, "Bottom window", ADISPLAY_ID_DEFAULT); mBottomWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}}); } protected: std::shared_ptr<FakeApplicationHandle> mApplication; sp<FakeWindowHandle> mNoInputWindow; sp<FakeWindowHandle> mBottomWindow; }; TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouches) { PointF touchedPoint = {10, 10}; NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchedPoint}); mDispatcher->notifyMotion(&motionArgs); mNoInputWindow->assertNoEvents(); // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have // an input channel, it is not marked as FLAG_NOT_TOUCHABLE, // and therefore should prevent mBottomWindow from receiving touches mBottomWindow->assertNoEvents(); } /** * If a window has feature NO_INPUT_CHANNEL, and somehow (by mistake) still has an input channel, * ensure that this window does not receive any touches, and blocks touches to windows underneath. */ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouchesWithValidChannel) { mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher, "Window with input channel and NO_INPUT_CHANNEL", ADISPLAY_ID_DEFAULT); mNoInputWindow->setInputFeatures(InputWindowInfo::Feature::NO_INPUT_CHANNEL); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}}); PointF touchedPoint = {10, 10}; NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchedPoint}); mDispatcher->notifyMotion(&motionArgs); mNoInputWindow->assertNoEvents(); mBottomWindow->assertNoEvents(); } } // namespace android::inputdispatcher