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

Commit 47cf0a08 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Add NO_FOCUS_CHANGE flag to pointer gestures to disallow focus changes

When using a multi-touch trackpad, it is the expected behavior in most
operating systems that the user is allowed to perform gestures (like
scroll, pinch, etc.) on an unfocused window without bringing it into
focus. The previous behavior in Android was that any DOWN event on an
unfocused window would bring the unfocused window into focus, including
any pointer gesture.

This change adds the NO_FOCUS_CHANGE flag to the MotionEvents generated
by certain pointer gestures so that it does not change window focus.
Gestures such as tap and tap drag are not affected.

Bug: 173733166
Test: atest inputflinger_tests
Test: manual: in multi-display scenario with freeform windows and a
trackpad: open two freeform windows so that they overlap, perform
gesture (scroll/pinch) on unfocused window, observe that the window is
not focused; perform tap on unfocused window, observe that the window is
focused.

Change-Id: I74e52f8daa13d4e6c047bc23982ec56942c555f6
parent 92ffb488
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -70,6 +70,14 @@ enum {
     */
    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8,

    /**
     * This flag indicates that the event will not cause a focus change if it is directed to an
     * unfocused window, even if it an ACTION_DOWN. This is typically used with pointer
     * gestures to allow the user to direct gestures to an unfocused window without bringing it
     * into focus.
     */
    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,

    /* Motion event is inconsistent with previously sent motion events. */
    AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
};
+6 −0
Original line number Diff line number Diff line
@@ -2907,6 +2907,12 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
                ATRACE_NAME(message.c_str());
            }

            if ((motionEntry.flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
                (motionEntry.policyFlags & POLICY_FLAG_TRUSTED)) {
                // Skip reporting pointer down outside focus to the policy.
                break;
            }

            dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
                                            inputTarget.inputChannel->getConnectionToken());

+14 −8
Original line number Diff line number Diff line
@@ -2456,6 +2456,12 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u
    int32_t metaState = getContext()->getGlobalMetaState();
    int32_t buttonState = mCurrentCookedState.buttonState;

    uint32_t flags = 0;

    if (!PointerGesture::canGestureAffectWindowFocus(mPointerGesture.currentGestureMode)) {
        flags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
    }

    // Update last coordinates of pointers that have moved so that we observe the new
    // pointer positions at the same time as other pointers that have just gone up.
    bool down = mPointerGesture.currentGestureMode == PointerGesture::Mode::TAP ||
@@ -2485,8 +2491,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u
    BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
    if (!dispatchedGestureIdBits.isEmpty()) {
        if (cancelPreviousGesture) {
            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0,
                           metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0,
                           flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                           mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
                           mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
                           mPointerGesture.downTime);
@@ -2504,7 +2510,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u
                uint32_t id = upGestureIdBits.clearFirstMarkedBit();

                dispatchMotion(when, readTime, policyFlags, mSource,
                               AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState,
                               AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, buttonState,
                               AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties,
                               mPointerGesture.lastGestureCoords,
                               mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0,
@@ -2517,7 +2523,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u

    // Send motion events for all pointers that moved.
    if (moveNeeded) {
        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, flags,
                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                       mPointerGesture.currentGestureProperties,
                       mPointerGesture.currentGestureCoords,
@@ -2538,7 +2544,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u
            }

            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
                           0, 0, metaState, buttonState, 0,
                           0, flags, metaState, buttonState, 0,
                           mPointerGesture.currentGestureProperties,
                           mPointerGesture.currentGestureCoords,
                           mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0,
@@ -2548,8 +2554,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u

    // Send motion events for hover.
    if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) {
        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
                       flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                       mPointerGesture.currentGestureProperties,
                       mPointerGesture.currentGestureCoords,
                       mPointerGesture.currentGestureIdToIndex,
@@ -2573,7 +2579,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u

        const int32_t displayId = mPointerController->getDisplayId();
        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags,
                              metaState, buttonState, MotionClassification::NONE,
                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
                              0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {});
+21 −0
Original line number Diff line number Diff line
@@ -590,6 +590,27 @@ private:
            QUIET,
        };

        // When a gesture is sent to an unfocused window, return true if it can bring that window
        // into focus, false otherwise.
        static bool canGestureAffectWindowFocus(Mode mode) {
            switch (mode) {
                case Mode::TAP:
                case Mode::TAP_DRAG:
                case Mode::BUTTON_CLICK_OR_DRAG:
                    // Taps can affect window focus.
                    return true;
                case Mode::FREEFORM:
                case Mode::HOVER:
                case Mode::NEUTRAL:
                case Mode::PRESS:
                case Mode::QUIET:
                case Mode::SWIPE:
                    // Most gestures can be performed on an unfocused window, so they should not
                    // not affect window focus.
                    return false;
            }
        }

        // Time the first finger went down.
        nsecs_t firstTouchTime;

+26 −1
Original line number Diff line number Diff line
@@ -1225,6 +1225,11 @@ public:
        return *this;
    }

    MotionEventBuilder& addFlag(uint32_t flags) {
        mFlags |= flags;
        return *this;
    }

    MotionEvent build() {
        std::vector<PointerProperties> pointerProperties;
        std::vector<PointerCoords> pointerCoords;
@@ -1244,7 +1249,7 @@ public:
        MotionEvent event;
        ui::Transform identityTransform;
        event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
                         mAction, mActionButton, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
                         mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                         mButtonState, MotionClassification::NONE, identityTransform,
                         /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
                         mRawYCursorPosition, mDisplayWidth, mDisplayHeight, mEventTime, mEventTime,
@@ -1260,6 +1265,7 @@ private:
    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
    int32_t mActionButton{0};
    int32_t mButtonState{0};
    int32_t mFlags{0};
    float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
    float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
    int32_t mDisplayWidth{AMOTION_EVENT_INVALID_DISPLAY_SIZE};
@@ -3105,6 +3111,25 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlr
    mFakePolicy->assertOnPointerDownWasNotCalled();
}

// Have two windows, one with focus. Injecting a trusted DOWN MotionEvent with the flag
// NO_FOCUS_CHANGE on the unfocused window should not call the onPointerDownOutsideFocus callback.
TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) {
    const MotionEvent event =
            MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
                    .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
                    .build();
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
    mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);

    ASSERT_TRUE(mDispatcher->waitForIdle());
    mFakePolicy->assertOnPointerDownWasNotCalled();
    // Ensure that the unfocused window did not receive any FOCUS events.
    mUnfocusedWindow->assertNoEvents();
}

// These tests ensures we can send touch events to a single client when there are multiple input
// windows that point to the same client token.
class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest {