Merge changes from topics "input-device-bluetooth-address", "notify-stylus-presence"
* changes:
InputReader: Get Bluetooth address from InputDeviceIdentifier
Determine the bluetooth address of an input device from its uniqueId
Notify the policy when a stylus gesture starts
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index ac9c5a5..e911734 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -58,6 +58,9 @@
// reuse values that are not associated with an input anymore.
uint16_t nonce;
+ // The bluetooth address of the device, if known.
+ std::optional<std::string> bluetoothAddress;
+
/**
* Return InputDeviceIdentifier.name that has been adjusted as follows:
* - all characters besides alphanumerics, dash,
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 55f730b..e24344b 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -24,16 +24,20 @@
namespace android {
template <typename T>
-std::string constToString(const T& v) {
+inline std::string constToString(const T& v) {
return std::to_string(v);
}
+inline std::string constToString(const std::string& s) {
+ return s;
+}
+
/**
* Convert an optional type to string.
*/
template <typename T>
-std::string toString(const std::optional<T>& optional,
- std::string (*toString)(const T&) = constToString) {
+inline std::string toString(const std::optional<T>& optional,
+ std::string (*toString)(const T&) = constToString) {
return optional ? toString(*optional) : "<not set>";
}
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index cacb63c..3b0f2ac 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -142,6 +142,9 @@
virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
/* Get light player ID */
virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
+
+ /* Get the Bluetooth address of an input device, if known. */
+ virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0;
};
// --- InputReaderConfiguration ---
@@ -393,6 +396,8 @@
/* Gets the affine calibration associated with the specified device. */
virtual TouchAffineTransformation getTouchAffineTransformation(
const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0;
+ /* Notifies the input reader policy that a stylus gesture has started. */
+ virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index b97c466..18d03f8 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -43,6 +43,7 @@
#include <ftl/enum.h>
#include <input/KeyCharacterMap.h>
#include <input/KeyLayoutMap.h>
+#include <input/PrintTools.h>
#include <input/VirtualKeyMap.h>
#include <openssl/sha.h>
#include <statslog.h>
@@ -134,10 +135,6 @@
{"green", LightColor::GREEN},
{"blue", LightColor::BLUE}};
-static inline const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
static std::string sha1(const std::string& in) {
SHA_CTX ctx;
SHA1_Init(&ctx);
@@ -2128,6 +2125,17 @@
identifier.uniqueId = buffer;
}
+ // Attempt to get the bluetooth address of an input device from the uniqueId.
+ if (identifier.bus == BUS_BLUETOOTH &&
+ std::regex_match(identifier.uniqueId,
+ std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) {
+ identifier.bluetoothAddress = identifier.uniqueId;
+ // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address.
+ for (auto& c : *identifier.bluetoothAddress) {
+ c = ::toupper(c);
+ }
+ }
+
// Fill in the descriptor.
assignDescriptorLocked(identifier);
@@ -2625,9 +2633,10 @@
dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber);
dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str());
dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
- "product=0x%04x, version=0x%04x\n",
+ "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n",
device->identifier.bus, device->identifier.vendor,
- device->identifier.product, device->identifier.version);
+ device->identifier.product, device->identifier.version,
+ toString(device->identifier.bluetoothAddress).c_str());
dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n",
device->keyMap.keyLayoutFile.c_str());
dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 428e999..f8b1b3f 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -58,6 +58,18 @@
identifier1.location == identifier2.location);
}
+static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
+ const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
+ if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+ actionMasked != AMOTION_EVENT_ACTION_DOWN &&
+ actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ return false;
+ }
+ const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action);
+ return motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
+ motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER;
+}
+
// --- InputReader ---
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
@@ -101,8 +113,10 @@
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
+ // Copy some state so that we can access it outside the lock later.
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
+ std::list<NotifyArgs> notifyArgs;
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -127,7 +141,7 @@
mReaderIsAliveCondition.notify_all();
if (!events.empty()) {
- notifyAll(processEventsLocked(events.data(), events.size()));
+ notifyArgs += processEventsLocked(events.data(), events.size());
}
if (mNextTimeout != LLONG_MAX) {
@@ -137,7 +151,7 @@
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
}
mNextTimeout = LLONG_MAX;
- notifyAll(timeoutExpiredLocked(now));
+ notifyArgs += timeoutExpiredLocked(now);
}
}
@@ -152,6 +166,16 @@
mPolicy->notifyInputDevicesChanged(inputDevices);
}
+ // Notify the policy of the start of every new stylus gesture outside the lock.
+ for (const auto& args : notifyArgs) {
+ const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
+ if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
+ mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+ }
+ }
+
+ notifyAll(std::move(notifyArgs));
+
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -851,6 +875,16 @@
return std::nullopt;
}
+std::optional<std::string> InputReader::getBluetoothAddress(int32_t deviceId) const {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->getBluetoothAddress();
+ }
+ return std::nullopt;
+}
+
bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index afb1bed..b9a2b4c 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -51,6 +51,9 @@
inline int32_t getGeneration() const { return mGeneration; }
inline const std::string getName() const { return mIdentifier.name; }
inline const std::string getDescriptor() { return mIdentifier.descriptor; }
+ inline std::optional<std::string> getBluetoothAddress() const {
+ return mIdentifier.bluetoothAddress;
+ }
inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
inline uint32_t getSources() const { return mSources; }
inline bool hasEventHubDevices() const { return !mDevices.empty(); }
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index de268cf..4f2503a 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -113,6 +113,8 @@
std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId) const override;
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index bc70584..f333306 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -244,6 +244,7 @@
bool mInputDevicesChanged GUARDED_BY(mLock){false};
std::vector<DisplayViewport> mViewports;
TouchAffineTransformation transform;
+ std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
protected:
virtual ~FakeInputReaderPolicy() {}
@@ -268,6 +269,18 @@
});
}
+ void assertStylusGestureNotified(int32_t deviceId) {
+ std::scoped_lock lock(mLock);
+ ASSERT_TRUE(mStylusGestureNotified);
+ ASSERT_EQ(deviceId, *mStylusGestureNotified);
+ mStylusGestureNotified.reset();
+ }
+
+ void assertStylusGestureNotNotified() {
+ std::scoped_lock lock(mLock);
+ ASSERT_FALSE(mStylusGestureNotified);
+ }
+
virtual void clearViewports() {
mViewports.clear();
mConfig.setDisplayViewports(mViewports);
@@ -428,6 +441,11 @@
ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
mInputDevicesChanged = false;
}
+
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override {
+ std::scoped_lock<std::mutex> lock(mLock);
+ mStylusGestureNotified = deviceId;
+ }
};
// --- FakeEventHub ---
@@ -2329,6 +2347,15 @@
mTestListener.reset();
mFakePolicy.clear();
}
+
+ std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) {
+ const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
+ const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(),
+ [&name](const InputDeviceInfo& info) {
+ return info.getIdentifier().name == name;
+ });
+ return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt;
+ }
};
TEST_F(InputReaderIntegrationTest, TestInvalidDevice) {
@@ -2450,6 +2477,9 @@
mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto info = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(info);
+ mDeviceInfo = *info;
}
void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
@@ -2473,6 +2503,7 @@
}
std::unique_ptr<UinputTouchScreen> mDevice;
+ InputDeviceInfo mDeviceInfo;
};
TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
@@ -2689,6 +2720,58 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
+TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Send down with the pen tool selected. The policy should be notified of the stylus presence.
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+
+ // Release the stylus touch.
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ // Touch down with the finger, without the pen tool selected. The policy is not notified.
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_FINGER);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter.
+ // The policy should be notified of the stylus presence.
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendMove(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+}
+
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
@@ -2699,6 +2782,7 @@
static const int32_t DEVICE_CONTROLLER_NUMBER;
static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
static const int32_t EVENTHUB_ID;
+ static const std::string DEVICE_BLUETOOTH_ADDRESS;
std::shared_ptr<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
@@ -2715,6 +2799,7 @@
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
identifier.location = DEVICE_LOCATION;
+ identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS;
mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
identifier);
mReader->pushNextDevice(mDevice);
@@ -2736,6 +2821,7 @@
const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK;
const int32_t InputDeviceTest::EVENTHUB_ID = 1;
+const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC";
TEST_F(InputDeviceTest, ImmutableProperties) {
ASSERT_EQ(DEVICE_ID, mDevice->getId());
@@ -3002,6 +3088,12 @@
device.dump(dumpStr, eventHubDevStr);
}
+TEST_F(InputDeviceTest, GetBluetoothAddress) {
+ const auto& address = mReader->getBluetoothAddress(DEVICE_ID);
+ ASSERT_TRUE(address);
+ ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
+}
+
// --- InputMapperTest ---
class InputMapperTest : public testing::Test {
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index ff7455b..e48f1d9 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -19,6 +19,7 @@
#include <android/input.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <input/Input.h>
namespace android {
@@ -62,6 +63,13 @@
return argPressure;
}
+MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
+ const auto argToolType = arg.pointerProperties[0].toolType;
+ *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got "
+ << motionToolTypeToString(argToolType);
+ return argToolType == toolType;
+}
+
MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
*result_listener << "expected flags " << flags << ", but got " << arg.flags;
return arg.flags == flags;
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index a23c873..626ad67 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -158,6 +158,8 @@
device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1;
device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN;
device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX;
+ device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER;
+ device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX;
}
void UinputTouchScreen::sendSlot(int32_t slot) {
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index a9f5a3a..2eed997 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -157,6 +157,10 @@
return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode);
}
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId) const {
+ return reader->getBluetoothAddress(deviceId);
+ }
+
private:
std::unique_ptr<InputReaderInterface> reader;
};
@@ -273,6 +277,7 @@
std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()),
std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()));
},
+ [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral<int32_t>()); },
})();
}
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index bd81761..64316ba 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -315,6 +315,7 @@
return mTransform;
}
void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; }
+ void notifyStylusGestureStarted(int32_t, nsecs_t) {}
};
class FuzzInputListener : public virtual InputListenerInterface {
@@ -363,6 +364,7 @@
void updateLedMetaState(int32_t metaState) override{};
int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
+ void notifyStylusGestureStarted(int32_t, nsecs_t) {}
};
} // namespace android