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

Commit 07e05b60 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

InputDispatcher: Implement spy windows

We implement spy windows as outlined in go/spy-windows.

Bug: 162194035
Test: atest inputflinger_tests
Change-Id: Iea3404329184ab40492666f2a66396e9d8cb3594
parent 713bb3e5
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -43,6 +43,10 @@ bool WindowInfo::supportsSplitTouch() const {
    return flags.test(Flag::SPLIT_TOUCH);
}

bool WindowInfo::isSpy() const {
    return inputFeatures.test(Feature::SPY);
}

bool WindowInfo::overlaps(const WindowInfo* other) const {
    return frameLeft < other->frameRight && frameRight > other->frameLeft &&
            frameTop < other->frameBottom && frameBottom > other->frameTop;
+3 −0
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ struct WindowInfo : public Parcelable {
        DISABLE_USER_ACTIVITY = 1u << 2,
        DROP_INPUT = 1u << 3,
        DROP_INPUT_IF_OBSCURED = 1u << 4,
        SPY = 1u << 5,
    };

    /* These values are filled in by the WM and passed through SurfaceFlinger
@@ -215,6 +216,8 @@ struct WindowInfo : public Parcelable {

    bool supportsSplitTouch() const;

    bool isSpy() const;

    bool overlaps(const WindowInfo* other) const;

    bool operator==(const WindowInfo& inputChannel) const;
+92 −40
Original line number Diff line number Diff line
@@ -1051,14 +1051,14 @@ sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayI
        LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
    }
    // Traverse windows from front to back to find touched window.
    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
    const auto& windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
        if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
            continue;
        }

        const WindowInfo& info = *windowHandle->getInfo();
        if (windowAcceptsTouchAt(info, displayId, x, y)) {
        if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y)) {
            return windowHandle;
        }

@@ -1070,6 +1070,27 @@ sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayI
    return nullptr;
}

std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(int32_t displayId,
                                                                                 int32_t x,
                                                                                 int32_t y) const {
    // Traverse windows from front to back and gather the touched spy windows.
    std::vector<sp<WindowInfoHandle>> spyWindows;
    const auto& windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
        const WindowInfo& info = *windowHandle->getInfo();

        if (!windowAcceptsTouchAt(info, displayId, x, y)) {
            continue;
        }
        if (!info.isSpy()) {
            // The first touched non-spy window was found, so return the spy windows touched so far.
            return spyWindows;
        }
        spyWindows.push_back(windowHandle);
    }
    return spyWindows;
}

void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason dropReason) {
    const char* reason;
    switch (dropReason) {
@@ -2078,9 +2099,11 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
            }
        }

        std::vector<sp<WindowInfoHandle>> newTouchedWindows;
        std::vector<sp<WindowInfoHandle>> newTouchedWindows =
                findTouchedSpyWindowsAtLocked(displayId, x, y);
        if (newTouchedWindowHandle != nullptr) {
            newTouchedWindows.push_back(newTouchedWindowHandle);
            // Process the foreground window first so that it is the first to receive the event.
            newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
        }

        for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
@@ -2128,8 +2151,10 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
            // Set target flags.
            int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_IS;

            if (!info.isSpy()) {
                // There should only be one new foreground (non-spy) window at this location.
                targetFlags |= InputTarget::FLAG_FOREGROUND;
            }

            if (isSplit) {
                targetFlags |= InputTarget::FLAG_SPLIT;
@@ -2268,10 +2293,17 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
    // Check permission to inject into all touched foreground windows and ensure there
    // is at least one touched foreground window.
    {
        bool haveForegroundWindow = false;
        bool haveForegroundOrSpyWindow = false;
        for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
            if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
                haveForegroundWindow = true;
            const bool isForeground =
                    (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0;
            if (touchedWindow.windowHandle->getInfo()->isSpy()) {
                haveForegroundOrSpyWindow = true;
                LOG_ALWAYS_FATAL_IF(isForeground,
                                    "Spy window cannot be dispatched as a foreground window.");
            }
            if (isForeground) {
                haveForegroundOrSpyWindow = true;
                if (!checkInjectionPermission(touchedWindow.windowHandle, entry.injectionState)) {
                    injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
                    injectionPermission = INJECTION_PERMISSION_DENIED;
@@ -2280,8 +2312,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
            }
        }
        bool hasGestureMonitor = !tempTouchState.gestureMonitors.empty();
        if (!haveForegroundWindow && !hasGestureMonitor) {
            ALOGI("Dropping event because there is no touched foreground window in display "
        if (!haveForegroundOrSpyWindow && !hasGestureMonitor) {
            ALOGI("Dropping event because there is no touched window in display "
                  "%" PRId32 " or gesture monitor to receive it.",
                  displayId);
            injectionResult = InputEventInjectionResult::FAILED;
@@ -5521,58 +5553,78 @@ void InputDispatcher::removeMonitorChannelLocked(
status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) {
    { // acquire lock
        std::scoped_lock _l(mLock);
        std::optional<int32_t> foundDisplayId = findGestureMonitorDisplayByTokenLocked(token);

        if (!foundDisplayId) {
            ALOGW("Attempted to pilfer pointers from an un-registered monitor or invalid token");
            return BAD_VALUE;
        }
        int32_t displayId = foundDisplayId.value();

        std::unordered_map<int32_t, TouchState>::iterator stateIt =
                mTouchStatesByDisplay.find(displayId);
        TouchState* statePtr = nullptr;
        std::shared_ptr<InputChannel> requestingChannel;
        int32_t displayId;
        int32_t deviceId;
        const std::optional<int32_t> foundGestureMonitorDisplayId =
                findGestureMonitorDisplayByTokenLocked(token);

        // TODO: Optimize this function for pilfering from windows when removing gesture monitors.
        if (foundGestureMonitorDisplayId) {
            // A gesture monitor has requested to pilfer pointers.
            displayId = *foundGestureMonitorDisplayId;
            auto stateIt = mTouchStatesByDisplay.find(displayId);
            if (stateIt == mTouchStatesByDisplay.end()) {
                ALOGW("Failed to pilfer pointers: no pointers on display %" PRId32 ".", displayId);
                return BAD_VALUE;
            }
            statePtr = &stateIt->second;

        TouchState& state = stateIt->second;
        std::shared_ptr<InputChannel> requestingChannel;
        std::optional<int32_t> foundDeviceId;
        for (const auto& monitor : state.gestureMonitors) {
            for (const auto& monitor : statePtr->gestureMonitors) {
                if (monitor.inputChannel->getConnectionToken() == token) {
                    requestingChannel = monitor.inputChannel;
                foundDeviceId = state.deviceId;
                    deviceId = statePtr->deviceId;
                }
            }
        } else {
            // Check if a window has requested to pilfer pointers.
            for (auto& [curDisplayId, state] : mTouchStatesByDisplay) {
                const sp<WindowInfoHandle>& windowHandle = state.getWindow(token);
                if (windowHandle != nullptr) {
                    displayId = curDisplayId;
                    requestingChannel = getInputChannelLocked(token);
                    deviceId = state.deviceId;
                    statePtr = &state;
                    break;
                }
            }
        }
        if (!foundDeviceId || !state.down) {
            ALOGW("Attempted to pilfer points from a monitor without any on-going pointer streams."

        if (requestingChannel == nullptr) {
            ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
            return BAD_VALUE;
        }
        TouchState& state = *statePtr;
        if (!state.down) {
            ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
                  " Ignoring.");
            return BAD_VALUE;
        }
        int32_t deviceId = foundDeviceId.value();

        // Send cancel events to all the input channels we're stealing from.
        CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
                                   "gesture monitor stole pointer stream");
                                   "input channel stole pointer stream");
        options.deviceId = deviceId;
        options.displayId = displayId;
        std::string canceledWindows = "[";
        std::string canceledWindows;
        for (const TouchedWindow& window : state.windows) {
            std::shared_ptr<InputChannel> channel =
                    getInputChannelLocked(window.windowHandle->getToken());
            if (channel != nullptr) {
            if (channel != nullptr && channel->getConnectionToken() != token) {
                synthesizeCancelationEventsForInputChannelLocked(channel, options);
                canceledWindows += channel->getName() + ", ";
                canceledWindows += canceledWindows.empty() ? "[" : ", ";
                canceledWindows += channel->getName();
            }
        }
        canceledWindows += "]";
        ALOGI("Monitor %s is stealing touch from %s", requestingChannel->getName().c_str(),
        canceledWindows += canceledWindows.empty() ? "[]" : "]";
        ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
              canceledWindows.c_str());

        // Then clear the current touch state so we stop dispatching to them as well.
        state.split = false;
        state.filterNonMonitors();
        state.filterWindowsExcept(token);
    }
    return OK;
}
+5 −0
Original line number Diff line number Diff line
@@ -238,6 +238,11 @@ private:
                                                                 bool ignoreDragWindow = false)
            REQUIRES(mLock);

    std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(int32_t displayId,
                                                                                  int32_t x,
                                                                                  int32_t y) const
            REQUIRES(mLock);

    sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
            REQUIRES(mLock);

+15 −2
Original line number Diff line number Diff line
@@ -105,8 +105,11 @@ void TouchState::filterNonAsIsTouchWindows() {
    }
}

void TouchState::filterNonMonitors() {
    windows.clear();
void TouchState::filterWindowsExcept(const sp<IBinder>& token) {
    auto it = std::remove_if(windows.begin(), windows.end(), [&token](const TouchedWindow& w) {
        return w.windowHandle->getToken() != token;
    });
    windows.erase(it, windows.end());
}

sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -144,4 +147,14 @@ sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
    return nullptr;
}

sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
    for (const TouchedWindow& touchedWindow : windows) {
        const auto& windowHandle = touchedWindow.windowHandle;
        if (windowHandle->getToken() == token) {
            return windowHandle;
        }
    }
    return nullptr;
}

} // namespace android::inputdispatcher
Loading