Stylus fusion: Use SOURCE_BLUETOOTH_STYLUS dynamically for events
Previously, whenever an external stylus was connected, the source for
all touch devices (touchscreens, styluses, drawing tablets, etc.) would
change to include SOURCE_BLUETOOTH_STYLUS. This meant that all events
produced by these devices would also include SOURCE_BLUETOOTH_STYLUS,
even if the event was unaltered by stylus fusion.
In this CL, we introduce a way to dynamically add
SOURCE_BLUETOOTH_STYLUS to event streams that are a product of stylus
fusion.
The problem we are trying to solve is to be able to differentiate a
normal event stream from a stylus vs. an event stream from a stylus or
touchscreen that has additional information fused from a bluetooth
device. Previously, both such streams would have
SOURCE_BLUETOOTH_STYLUS, whereas now, only the latter case would use
that source.
Bug: 300473125
Test: atest inputflinger_test
Change-Id: I3526d42df68bc899c8e9a0e5ad69c95864f4c325
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index b565454..90bd7c9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -130,7 +130,10 @@
TouchInputMapper::~TouchInputMapper() {}
uint32_t TouchInputMapper::getSources() const {
- return mSource;
+ // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified
+ // by the external stylus state. That's why we don't add it directly to mSource during
+ // configuration.
+ return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0);
}
void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
@@ -932,9 +935,6 @@
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
- if (hasExternalStylus()) {
- mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
- }
} else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
mDeviceMode = DeviceMode::NAVIGATION;
@@ -1664,6 +1664,10 @@
mSource, mViewport.displayId, policyFlags,
mLastCookedState.buttonState, mCurrentCookedState.buttonState);
+ if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+ mCurrentStreamModifiedByExternalStylus = false;
+ }
+
// Clear some transient state.
mCurrentRawState.rawVScroll = 0;
mCurrentRawState.rawHScroll = 0;
@@ -1715,6 +1719,10 @@
mExternalStylusButtonsApplied |= pressedButtons;
mExternalStylusButtonsApplied &= ~releasedButtons;
+
+ if (mExternalStylusButtonsApplied != 0 || releasedButtons != 0) {
+ mCurrentStreamModifiedByExternalStylus = true;
+ }
}
}
@@ -1725,6 +1733,8 @@
return;
}
+ mCurrentStreamModifiedByExternalStylus = true;
+
float pressure = lastPointerData.isTouching(*mFusedStylusPointerId)
? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId)
.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)
@@ -3821,6 +3831,9 @@
ALOG_ASSERT(false);
}
}
+ if (mCurrentStreamModifiedByExternalStylus) {
+ source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
+ }
const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index c5dfb00..bd9371d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -357,6 +357,8 @@
bool mExternalStylusDataPending;
// A subset of the buttons in mCurrentRawState that came from an external stylus.
int32_t mExternalStylusButtonsApplied{0};
+ // True if the current cooked pointer data was modified due to the state of an external stylus.
+ bool mCurrentStreamModifiedByExternalStylus{false};
// True if we sent a HOVER_ENTER event.
bool mSentHoverEnter{false};
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index bce0937..6539593 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -91,6 +91,9 @@
static constexpr int32_t ACTION_POINTER_1_UP =
AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr uint32_t STYLUS_FUSION_SOURCE =
+ AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
+
// Minimum timestamp separation between subsequent input events from a Bluetooth device.
static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
// Maximum smoothing time delta so that we don't generate events too far into the future.
@@ -2308,6 +2311,22 @@
// ongoing stylus gesture that is being emitted by the touchscreen.
using ExternalStylusIntegrationTest = BaseTouchIntegrationTest;
+TEST_F(ExternalStylusIntegrationTest, ExternalStylusConnectionChangesTouchscreenSource) {
+ // Create an external stylus capable of reporting pressure data that
+ // should be fused with a touch pointer.
+ std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+ createUinputDevice<UinputExternalStylusWithPressure>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ // Connecting an external stylus changes the source of the touchscreen.
+ const auto deviceInfo = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(deviceInfo);
+ ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
+}
+
TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
const Point centerPoint = mDevice->getCenterPoint();
@@ -2337,17 +2356,17 @@
mDevice->sendDown(centerPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
- AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
- WithToolType(ToolType::STYLUS), WithButtonState(0),
- WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::STYLUS),
+ WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+ WithPressure(100.f / RAW_PRESSURE_MAX))));
// Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
// event with the updated pressure.
stylus->setPressure(200);
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
- AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
- WithToolType(ToolType::STYLUS), WithButtonState(0),
- WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithToolType(ToolType::STYLUS),
+ WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+ WithPressure(200.f / RAW_PRESSURE_MAX))));
// The external stylus did not generate any events.
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2392,8 +2411,8 @@
// it shows up as a finger pointer.
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
- WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId),
- WithPressure(1.f))));
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+ WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f))));
// Change the pressure on the external stylus. Since the pressure was not present at the start
// of the gesture, it is ignored for now.
@@ -2405,6 +2424,7 @@
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
WithToolType(ToolType::FINGER))));
// Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
@@ -2413,9 +2433,9 @@
mDevice->sendDown(centerPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
- AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
- WithToolType(ToolType::STYLUS), WithButtonState(0),
- WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(STYLUS_FUSION_SOURCE),
+ WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId),
+ WithPressure(200.f / RAW_PRESSURE_MAX))));
// The external stylus did not generate any events.
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2447,14 +2467,15 @@
std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(
- mTestListener
- ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
- WithToolType(
- ToolType::FINGER),
- WithButtonState(0),
- WithDeviceId(touchscreenId),
- WithPressure(1.f)),
- waitUntil));
+ mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction(
+ AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(ToolType::FINGER),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+ AINPUT_SOURCE_STYLUS),
+ WithButtonState(0),
+ WithDeviceId(touchscreenId),
+ WithPressure(1.f)),
+ waitUntil));
// The external stylus did not generate any events.
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -7567,12 +7588,10 @@
protected:
StylusState mStylusState{};
- static constexpr uint32_t EXPECTED_SOURCE =
- AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
auto toolTypeSource =
- AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+ AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
// The first pointer is withheld.
processDown(mapper, 100, 200);
@@ -7606,7 +7625,7 @@
processUp(mapper);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
- AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
WithToolType(ToolType::STYLUS))));
mStylusState.pressure = 0.f;
@@ -7616,8 +7635,10 @@
}
void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+ // When stylus fusion is not successful, events should be reported with the original source.
+ // In this case, it is from a touchscreen.
auto toolTypeSource =
- AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER));
+ AllOf(WithSource(AINPUT_SOURCE_TOUCHSCREEN), WithToolType(ToolType::FINGER));
// The first pointer is withheld when an external stylus is connected,
// and a timeout is requested.
@@ -7657,7 +7678,7 @@
TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
- ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources());
+ ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources());
}
TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
@@ -7674,8 +7695,7 @@
// before the touch is reported by the touchscreen.
TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) {
SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
- auto toolTypeSource =
- AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+ auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
// The external stylus reports pressure first. It is ignored for now.
mStylusState.pressure = 1.f;
@@ -7717,8 +7737,7 @@
TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
- auto toolTypeSource =
- AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+ auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
mStylusState.pressure = 0.8f;
processExternalStylusState(mapper);
@@ -7779,7 +7798,7 @@
processUp(mapper);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
- AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
WithToolType(ToolType::STYLUS))));
ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
@@ -7788,7 +7807,7 @@
TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) {
SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
- auto source = WithSource(EXPECTED_SOURCE);
+ auto source = WithSource(STYLUS_FUSION_SOURCE);
mStylusState.pressure = 1.f;
mStylusState.toolType = ToolType::ERASER;
@@ -7841,8 +7860,7 @@
TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
- auto toolTypeSource =
- AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+ auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));