diff options
author | 2023-03-02 15:26:39 +0000 | |
---|---|---|
committer | 2023-03-07 15:42:35 +0000 | |
commit | 3ca39d03b022d2b0ca5a35ac249de1876749d79b (patch) | |
tree | 639f89ca49253a63fb01a227e1a48129ff7c6d4a | |
parent | 186fb6f20c86dd771d17a43b5cb2dc0744aae36c (diff) |
Support touchpad gesture properties in IDC files
Specific touchpads often require small tweaks to the Gestures library's
gesture properties to work well. Examples of such tweaks include palm
thresholds, or the parameters to the stationary wiggle filter. To
support this, allow properties to be set using the 'gestureProp.' prefix
in IDC files, with the spaces in property names replaced by underscores.
For example, to set the "Pressure Calibration Offset" property to 30,
add this line to the relevant IDC file:
gestureProp.Pressure_Calibration_Offset = 30
Only single-valued boolean, integer, or real properties can be set this
way. There's currently no use case for strings. The only use case for
arrays is for acceleration curves, which wouldn't work well with this
system anyway since they'd be overridden by the pointer speed settings.
Bug: 271251605
Test: atest inputflinger_tests
Test: connect Apple Magic Trackpad 2, check properties are set correctly
in dumpsys input
Change-Id: I1bf97c9753e48c00d3dc3098cb676f7baebc84ce
-rw-r--r-- | include/input/PropertyMap.h | 7 | ||||
-rw-r--r-- | libs/input/Android.bp | 1 | ||||
-rw-r--r-- | libs/input/PropertyMap.cpp | 29 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 6 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp | 76 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/gestures/PropertyProvider.h | 6 | ||||
-rw-r--r-- | services/inputflinger/tests/PropertyProvider_test.cpp | 65 | ||||
-rw-r--r-- | services/inputflinger/tests/TestConstants.h | 4 |
8 files changed, 194 insertions, 0 deletions
diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index 28e4816afe..18ce16df36 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -18,7 +18,10 @@ #include <android-base/result.h> #include <utils/Tokenizer.h> + +#include <string> #include <unordered_map> +#include <unordered_set> namespace android { @@ -57,6 +60,9 @@ public: */ void addProperty(const std::string& key, const std::string& value); + /* Returns a set of all property keys starting with the given prefix. */ + std::unordered_set<std::string> getKeysWithPrefix(const std::string& prefix) const; + /* Gets the value of a property and parses it. * Returns true and sets outValue if the key was found and its value was parsed successfully. * Otherwise returns false and does not modify outValue. (Also logs a warning.) @@ -65,6 +71,7 @@ public: bool tryGetProperty(const std::string& key, bool& outValue) const; bool tryGetProperty(const std::string& key, int32_t& outValue) const; bool tryGetProperty(const std::string& key, float& outValue) const; + bool tryGetProperty(const std::string& key, double& outValue) const; /* Adds all values from the specified property map. */ void addAll(const PropertyMap* map); diff --git a/libs/input/Android.bp b/libs/input/Android.bp index f38dd98428..869458c407 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -167,6 +167,7 @@ cc_library { cc_defaults { name: "libinput_fuzz_defaults", + cpp_std: "c++20", host_supported: true, shared_libs: [ "libutils", diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index ed9ac9fc72..9a4f10b21d 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "PropertyMap" +#include <cstdlib> + #include <input/PropertyMap.h> #include <log/log.h> @@ -44,6 +46,16 @@ void PropertyMap::addProperty(const std::string& key, const std::string& value) mProperties.emplace(key, value); } +std::unordered_set<std::string> PropertyMap::getKeysWithPrefix(const std::string& prefix) const { + std::unordered_set<std::string> keys; + for (const auto& [key, _] : mProperties) { + if (key.starts_with(prefix)) { + keys.insert(key); + } + } + return keys; +} + bool PropertyMap::hasProperty(const std::string& key) const { return mProperties.find(key) != mProperties.end(); } @@ -102,6 +114,23 @@ bool PropertyMap::tryGetProperty(const std::string& key, float& outValue) const return true; } +bool PropertyMap::tryGetProperty(const std::string& key, double& outValue) const { + std::string stringValue; + if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) { + return false; + } + + char* end; + double value = strtod(stringValue.c_str(), &end); + if (*end != '\0') { + ALOGW("Property key '%s' has invalid value '%s'. Expected a double.", key.c_str(), + stringValue.c_str()); + return false; + } + outValue = value; + return true; +} + void PropertyMap::addAll(const PropertyMap* map) { for (const auto& [key, value] : map->mProperties) { mProperties.emplace(key, value); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index d3af402153..330976719d 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -20,6 +20,7 @@ #include <optional> #include <android/input.h> +#include <ftl/enum.h> #include <input/PrintTools.h> #include <linux/input-event-codes.h> #include <log/log_main.h> @@ -216,6 +217,11 @@ void TouchpadInputMapper::dump(std::string& dump) { std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { + if (!changes) { + // First time configuration + mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration()); + } + if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { std::optional<int32_t> displayId = mPointerController->getDisplayId(); ui::Rotation orientation = ui::ROTATION_0; diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index 089f45a4e6..3d883389c9 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -84,6 +84,29 @@ std::string PropertyProvider::dump() const { return dump; } +void PropertyProvider::loadPropertiesFromIdcFile(const PropertyMap& idcProperties) { + // For compatibility with the configuration file syntax, gesture property names in IDC files are + // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the + // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property. + const std::string gesturePropPrefix = "gestureProp."; + for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) { + std::string propertyName = key.substr(gesturePropPrefix.length()); + for (size_t i = 0; i < propertyName.length(); i++) { + if (propertyName[i] == '_') { + propertyName[i] = ' '; + } + } + + auto it = mProperties.find(propertyName); + if (it != mProperties.end()) { + it->second.trySetFromIdcProperty(idcProperties, key); + } else { + ALOGE("Gesture property \"%s\" specified in IDC file does not exist for this device.", + propertyName.c_str()); + } + } +} + GesturesProp* PropertyProvider::createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init) { const auto [it, inserted] = @@ -211,6 +234,59 @@ void GesturesProp::setRealValues(const std::vector<double>& values) { setValues(std::get<double*>(mDataPointer), values); } +namespace { + +// Helper to std::visit with lambdas. +template <typename... V> +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template <typename... V> +Visitor(V...) -> Visitor<V...>; + +} // namespace + +void GesturesProp::trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName) { + if (mCount != 1) { + ALOGE("Gesture property \"%s\" is an array, and so cannot be set in an IDC file.", + mName.c_str()); + return; + } + bool parsedSuccessfully = false; + Visitor setVisitor{ + [&](int*) { + int32_t value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setIntValues({value}); + } + }, + [&](GesturesPropBool*) { + bool value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setBoolValues({value}); + } + }, + [&](double*) { + double value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setRealValues({value}); + } + }, + [&](const char**) { + ALOGE("Gesture property \"%s\" is a string, and so cannot be set in an IDC file.", + mName.c_str()); + }, + }; + std::visit(setVisitor, mDataPointer); + + ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" could set due to a type mismatch.", + mName.c_str()); + return; +} + template <typename T, typename U> const std::vector<T> GesturesProp::getValues(U* dataPointer) const { if (mGetter != nullptr) { diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h index 50451a3929..c7e0858c6d 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -22,6 +22,7 @@ #include <vector> #include "include/gestures.h" +#include "input/PropertyMap.h" namespace android { @@ -35,6 +36,8 @@ public: GesturesProp& getProperty(const std::string& name); std::string dump() const; + void loadPropertiesFromIdcFile(const PropertyMap& idcProperties); + // Methods to be called by the gestures library: GesturesProp* createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init); @@ -83,6 +86,9 @@ public: // Setting string values isn't supported since we don't have a use case yet and the memory // management adds additional complexity. + void trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName); + private: // Two type parameters are required for these methods, rather than one, due to the gestures // library using its own bool type. diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp index 42a6a9f4b9..8a40e78b89 100644 --- a/services/inputflinger/tests/PropertyProvider_test.cpp +++ b/services/inputflinger/tests/PropertyProvider_test.cpp @@ -18,6 +18,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "TestConstants.h" #include "include/gestures.h" namespace android { @@ -283,4 +284,68 @@ TEST_F(PropertyProviderTest, Free) { EXPECT_FALSE(mProvider.hasProperty("Foo")); } +class PropertyProviderIdcLoadingTest : public testing::Test { +protected: + void SetUp() override { + int initialInt = 0; + GesturesPropBool initialBool = false; + double initialReal = 0.0; + gesturePropProvider.create_int_fn(&mProvider, "An Integer", &mIntData, 1, &initialInt); + gesturePropProvider.create_bool_fn(&mProvider, "A Boolean", &mBoolData, 1, &initialBool); + gesturePropProvider.create_real_fn(&mProvider, "A Real", &mRealData, 1, &initialReal); + } + + PropertyProvider mProvider; + + int mIntData; + GesturesPropBool mBoolData; + double mRealData; +}; + +TEST_F(PropertyProviderIdcLoadingTest, AllCorrect) { + PropertyMap idcProps; + idcProps.addProperty("gestureProp.An_Integer", "42"); + idcProps.addProperty("gestureProp.A_Boolean", "1"); + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(42)); + EXPECT_THAT(mProvider.getProperty("A Boolean").getBoolValues(), ElementsAre(true)); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, InvalidPropsIgnored) { + int intArrayData[2]; + int initialInts[2] = {0, 1}; + gesturePropProvider.create_int_fn(&mProvider, "Two Integers", intArrayData, 2, initialInts); + + PropertyMap idcProps; + // Wrong type + idcProps.addProperty("gestureProp.An_Integer", "37.25"); + // Wrong size + idcProps.addProperty("gestureProp.Two_Integers", "42"); + // Doesn't exist + idcProps.addProperty("gestureProp.Some_Nonexistent_Property", "1"); + // A valid assignment that should still be applied despite the others being invalid + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(0)); + EXPECT_THAT(mProvider.getProperty("Two Integers").getIntValues(), ElementsAre(0, 1)); + EXPECT_FALSE(mProvider.hasProperty("Some Nonexistent Property")); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, FunkyName) { + int data; + int initialData = 0; + gesturePropProvider.create_int_fn(&mProvider, " I lOvE sNAKes ", &data, 1, &initialData); + + PropertyMap idcProps; + idcProps.addProperty("gestureProp.__I_lOvE_sNAKes_", "42"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty(" I lOvE sNAKes ").getIntValues(), ElementsAre(42)); +} + } // namespace android diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h index 27881f6f49..ad48b0fbe0 100644 --- a/services/inputflinger/tests/TestConstants.h +++ b/services/inputflinger/tests/TestConstants.h @@ -16,6 +16,10 @@ #pragma once +#include <chrono> + +#include <utils/Timers.h> + namespace android { using std::chrono_literals::operator""ms; |