Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2862a07 authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

Ensure window with NO_INPUT_CHANNEL drops touches

Current behaviour of the system is to drop touches when a window with
feature NO_INPUT_CHANNEL is being touched.

Whether or not this behaviour is correct, first add a test to highlight
the current behaviour.

Bug: 160305383 160425280
Test: atest inputflinger_tests:InputDispatcherMultiWindowOcclusionTests#NoInputChannelFeature_DropsTouches
Test: adb logcat | grep -i input
07-31 17:00:35.499 31208 31213 W InputDispatcher: Window without input channel will not receive the new gesture at 5781842196550
07-31 17:00:35.499 31208 31213 I InputDispatcher: Dropping event because there is no touchable window or gesture monitor at (10, 10) in display 0.

Test: atest inputflinger_tests:InputDispatcherMultiWindowOcclusionTests#NoInputChannelFeature_DropsTouchesWithValidChannel

logs:
07-31 16:58:02.692 30773 30773 E InputDispatcher: Window with input channel and NO_INPUT_CHANNEL has feature NO_INPUT_WINDOW, but a non-null token. Clearing
07-31 16:58:02.693 30773 30778 W InputDispatcher: Window with input channel and NO_INPUT_CHANNEL will not receive the new gesture at 5629036343568
07-31 16:58:02.693 30773 30778 I InputDispatcher: Dropping event because there is no touchable window or gesture monitor at (10, 10) in display 0.

Change-Id: Ied554c3a2100d74ef5fea74897301dded5dc1416
parent cd59d0a8
Loading
Loading
Loading
Loading
+38 −8
Original line number Diff line number Diff line
@@ -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;
            }
@@ -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);
@@ -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);

+1 −0
Original line number Diff line number Diff line
@@ -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.
+84 −5
Original line number Diff line number Diff line
@@ -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);
@@ -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;
@@ -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);
    }
@@ -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();
    }

@@ -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