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

Commit 3ad54f58 authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

inputdispatcher: Block touch while stylus is hovering

Before this CL, we only blocked touch when stylus was down. In this CL,
we change the behaviour to match the current behaviour inside
PreferStylusOverTouchBlocker: while stylus is hovering, reject touch.

That means:
1) While stylus is hovering, touch is ignored
2) If stylus starts hovering when touch is down, cancel touch

Bug: 308745008
Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
Change-Id: I3ebbc71753d67ef4603f8f018c529cb0474cd19e
parent 371808bf
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -67,6 +67,16 @@ const bool DEBUG_OUTBOUND_MOTION =
const bool DEBUG_MODEL =
        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);

/**
 * When multi-device input is enabled, we shouldn't use PreferStylusOverTouchBlocker at all.
 * However, multi-device input has the following default behaviour: hovering stylus rejects touch.
 * Therefore, if we want to disable that behaviour (and go back to a place where stylus down
 * blocks touch, but hovering stylus doesn't interact with touch), we should just disable the entire
 * multi-device input feature.
 */
const bool ENABLE_MULTI_DEVICE_INPUT = input_flags::enable_multi_device_input() &&
        !input_flags::disable_reject_touch_on_stylus_hover();

// Category (=namespace) name for the input settings that are applied at boot time
static const char* INPUT_NATIVE_BOOT = "input_native_boot";
/**
@@ -347,7 +357,7 @@ void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
    ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
    { // acquire lock
        std::scoped_lock lock(mLock);
        if (input_flags::enable_multi_device_input()) {
        if (ENABLE_MULTI_DEVICE_INPUT) {
            notifyMotionLocked(args);
        } else {
            const std::vector<NotifyMotionArgs> processedArgs =
+5 −31
Original line number Diff line number Diff line
@@ -24,19 +24,6 @@

namespace android::inputdispatcher {

namespace {
bool isHoverAction(int32_t action) {
    switch (MotionEvent::getActionMasked(action)) {
        case AMOTION_EVENT_ACTION_HOVER_ENTER:
        case AMOTION_EVENT_ACTION_HOVER_MOVE:
        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
            return true;
        }
    }
    return false;
}
} // namespace

InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}

InputState::~InputState() {}
@@ -113,13 +100,6 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f
        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
            !isStylusEvent(entry.source, entry.pointerProperties)) {
            // We already have a stylus stream, and the new event is not from stylus.
            if (!lastMemento.hovering) {
                // If stylus is currently down, reject the new event unconditionally.
                return false;
            }
        }
        if (!lastMemento.hovering && isHoverAction(action)) {
            // Reject hovers if already down
            return false;
        }
    }
@@ -366,19 +346,13 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry,
        return false;
    }

    // We want stylus down to block touch and other source types, but stylus hover should not
    // have such an effect.
    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
        // New event is a hover. Keep the current non-hovering gesture instead
        return false;
    }

    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
        // We have non-hovering stylus already active.
    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
        // A stylus is already active.
        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
            // If this new event is a stylus from a different device going down, then cancel the old
            // stylus and allow the new stylus to take over
            // If this new event is from a different device, then cancel the old
            // stylus and allow the new stylus to take over, but only if it's going down.
            // Otherwise, they will start to race each other.
            return true;
        }

+34 −38
Original line number Diff line number Diff line
@@ -2593,9 +2593,9 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
/**
 * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
 * touch is not dropped, because stylus hover should be ignored.
 * touch is dropped, because stylus hover takes precedence.
 */
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
    sp<FakeWindowHandle> window =
            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2624,34 +2624,29 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                      .build());
    // Stylus hover is canceled because touch is down
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
                                     WithCoords(140, 145)));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
                                     WithCoords(141, 146)));
    // Touch is ignored because stylus is hovering
    // Subsequent stylus movements are ignored
    // Subsequent stylus movements are delivered correctly
    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                      .deviceId(stylusDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                      .build());
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
    // but subsequent touches continue to be delivered
    // and subsequent touches continue to be ignored
    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                      .deviceId(touchDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                      .build());
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
                                     WithCoords(142, 147)));
    window->assertNoEvents();
}
/**
 * One window. Touch down on the window. Then, stylus hover on the window from another device.
 * Ensure that touch is not canceled, because stylus hover should be dropped.
 * Ensure that touch is canceled, because stylus hover should take precedence.
 */
TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
    sp<FakeWindowHandle> window =
            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2683,15 +2678,21 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
                                      .deviceId(stylusDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                      .build());
    // Stylus hover movement is dropped
    // Stylus hover movement causes touch to be canceled
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
                                     WithCoords(141, 146)));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
    // Subsequent touch movements are ignored
    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                      .deviceId(touchDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                      .build());
    // Subsequent touch movements are delivered correctly
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
                                     WithCoords(142, 147)));
    window->assertNoEvents();
}
/**
@@ -3008,11 +3009,11 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
 * Three windows: a window on the left, a window on the right, and a spy window positioned above
 * both.
 * Check hover in left window and touch down in the right window.
 * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
 * At first, spy should receive hover. Spy shouldn't receive touch while stylus is hovering.
 * At the same time, left and right should be getting independent streams of hovering and touch,
 * respectively.
 */
TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
    sp<FakeWindowHandle> spyWindow =
@@ -3052,28 +3053,25 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                      .build());
    leftWindow->assertNoEvents();
    spyWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
    spyWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
    spyWindow->assertNoEvents();
    rightWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
    // Stylus movements continue. They should be delivered to the left window only.
    // Stylus movements continue. They should be delivered to the left window and the spy.
    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                      .deviceId(stylusDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                      .build());
    leftWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
    spyWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
    // Touch movements continue. They should be delivered to the right window and to the spy
    // Touch movements continue. They should be delivered to the right window only
    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                      .deviceId(touchDeviceId)
                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
                                      .build());
    spyWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
    rightWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
@@ -3288,7 +3286,7 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
 * While the touch is down, new hover events from the stylus device should be ignored. After the
 * touch is gone, stylus hovering should start working again.
 */
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
    sp<FakeWindowHandle> window =
            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3314,10 +3312,7 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
                                        .deviceId(touchDeviceId)
                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                        .build()));
    // The touch device should cause hover to stop!
    window->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
    // The touch device should be ignored!
    // Continue hovering with stylus.
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3327,7 +3322,9 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
                                        .deviceId(stylusDeviceId)
                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                        .build()));
    // Hovers are now ignored
    // Hovers continue to work
    window->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
    // Lift up the finger
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3337,7 +3334,6 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
                                        .deviceId(touchDeviceId)
                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                        .build()));
    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
              injectMotionEvent(*mDispatcher,
@@ -3346,8 +3342,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
                                        .deviceId(stylusDeviceId)
                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
                                        .build()));
    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                     WithDeviceId(stylusDeviceId)));
    window->consumeMotionEvent(
            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
    window->assertNoEvents();
}