diff options
13 files changed, 714 insertions, 9 deletions
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index a20544b5fd..02bc2010db 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -21,6 +21,7 @@ #include <optional> #include <set> #include <string> +#include <vector> namespace android { @@ -34,6 +35,16 @@ inline std::string constToString(const T& v) { return std::to_string(v); } +template <> +inline std::string constToString(const bool& value) { + return value ? "true" : "false"; +} + +template <> +inline std::string constToString(const std::vector<bool>::reference& value) { + return value ? "true" : "false"; +} + inline std::string constToString(const std::string& s) { return s; } @@ -76,6 +87,19 @@ std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const return out; } +/** + * Convert a vector to a string. The values of the vector should be of a type supported by + * constToString. + */ +template <typename T> +std::string dumpVector(std::vector<T> values) { + std::string dump = constToString(values[0]); + for (size_t i = 1; i < values.size(); i++) { + dump += ", " + constToString(values[i]); + } + return dump; +} + const char* toString(bool value); /** @@ -87,4 +111,4 @@ const char* toString(bool value); */ std::string addLinePrefix(std::string str, const std::string& prefix); -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index f3b680bc29..d29692cf05 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -64,6 +64,7 @@ filegroup { "mapper/gestures/GestureConverter.cpp", "mapper/gestures/GesturesLogging.cpp", "mapper/gestures/HardwareStateConverter.cpp", + "mapper/gestures/PropertyProvider.cpp", ], } diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 43b67cae8b..f7b38a1b9e 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -515,6 +515,18 @@ ftl::Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis, return deviceClasses & InputDeviceClass::JOYSTICK; } +// --- RawAbsoluteAxisInfo --- + +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) { + if (info.valid) { + out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat + << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution; + } else { + out << "unknown range"; + } + return out; +} + // --- EventHub::Device --- EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index a3ecf418bb..86acadb428 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -19,6 +19,8 @@ #include <bitset> #include <climits> #include <filesystem> +#include <ostream> +#include <string> #include <unordered_map> #include <utility> #include <vector> @@ -77,6 +79,8 @@ struct RawAbsoluteAxisInfo { inline void clear() { *this = RawAbsoluteAxisInfo(); } }; +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info); + /* * Input device classes. */ diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 8e3539c3a6..ba2ea998b5 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -18,6 +18,8 @@ #include "InputMapper.h" +#include <sstream> + #include "InputDevice.h" #include "input/PrintTools.h" @@ -120,12 +122,9 @@ void InputMapper::bumpGeneration() { void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis, const char* name) { - if (axis.valid) { - dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name, - axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution); - } else { - dump += StringPrintf(INDENT4 "%s: unknown range\n", name); - } + std::stringstream out; + out << INDENT4 << name << ": " << axis << "\n"; + dump += out.str(); } void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 3b51be83ab..b6313a1049 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -19,6 +19,7 @@ #include <optional> #include <android/input.h> +#include <input/PrintTools.h> #include <linux/input-event-codes.h> #include <log/log_main.h> #include "TouchCursorInputMapperCommon.h" @@ -96,11 +97,12 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); // Even though we don't explicitly delete copy/move semantics, it's safe to - // give away a pointer to TouchpadInputMapper here because + // give away pointers to TouchpadInputMapper and its members here because // 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and // 2) TouchpadInputMapper is stored as a unique_ptr and not moved. + mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider), + &mPropertyProvider); mGestureInterpreter->SetCallback(gestureInterpreterCallback, this); - // TODO(b/251196347): set a property provider, so we can change gesture properties. // TODO(b/251196347): set a timer provider, so the library can use timers. } @@ -108,12 +110,29 @@ TouchpadInputMapper::~TouchpadInputMapper() { if (mPointerController != nullptr) { mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } + + // The gesture interpreter's destructor will call its property provider's free function for all + // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer + // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may + // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on + // declaration order to avoid crashes seems rather fragile, so explicitly clear the property + // provider here to ensure all the freeProperty calls happen before mPropertyProvider is + // destructed. + mGestureInterpreter->SetPropProvider(nullptr, nullptr); } uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } +void TouchpadInputMapper::dump(std::string& dump) { + dump += INDENT2 "Touchpad Input Mapper:\n"; + dump += INDENT3 "Gesture converter:\n"; + dump += addLinePrefix(mGestureConverter.dump(), INDENT4); + dump += INDENT3 "Gesture properties:\n"; + dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); +} + std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 3a922115a1..d693bcaf30 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -29,6 +29,7 @@ #include "NotifyArgs.h" #include "gestures/GestureConverter.h" #include "gestures/HardwareStateConverter.h" +#include "gestures/PropertyProvider.h" #include "include/gestures.h" @@ -40,6 +41,8 @@ public: ~TouchpadInputMapper(); uint32_t getSources() const override; + void dump(std::string& dump) override; + [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; @@ -57,6 +60,8 @@ private: mGestureInterpreter; std::shared_ptr<PointerControllerInterface> mPointerController; + PropertyProvider mPropertyProvider; + HardwareStateConverter mStateConverter; GestureConverter mGestureConverter; diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 11ffd286dd..561b1f819a 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -16,7 +16,11 @@ #include "gestures/GestureConverter.h" +#include <sstream> + +#include <android-base/stringprintf.h> #include <android/input.h> +#include <ftl/enum.h> #include <linux/input-event-codes.h> #include <log/log_main.h> @@ -55,6 +59,18 @@ GestureConverter::GestureConverter(InputReaderContext& readerContext, deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); } +std::string GestureConverter::dump() const { + std::stringstream out; + out << "Orientation: " << ftl::enum_string(mOrientation) << "\n"; + out << "Axis info:\n"; + out << " X: " << mXAxisInfo << "\n"; + out << " Y: " << mYAxisInfo << "\n"; + out << StringPrintf("Button state: 0x%08x\n", mButtonState); + out << "Down time: " << mDownTime << "\n"; + out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n"; + return out.str(); +} + void GestureConverter::reset() { mButtonState = 0; } diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 8e8e3d9de3..2ec5841fe0 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -40,6 +40,8 @@ public: GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, int32_t deviceId); + std::string dump() const; + void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } void reset(); diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp new file mode 100644 index 0000000000..cd18cd3e35 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -0,0 +1,236 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Macros.h" + +#include "gestures/PropertyProvider.h" + +#include <algorithm> +#include <utility> + +#include <android-base/stringprintf.h> +#include <ftl/enum.h> +#include <input/PrintTools.h> +#include <log/log_main.h> + +namespace android { + +namespace { + +GesturesProp* createInt(void* data, const char* name, int* loc, size_t count, const int* init) { + return static_cast<PropertyProvider*>(data)->createIntArrayProperty(name, loc, count, init); +} + +GesturesProp* createBool(void* data, const char* name, GesturesPropBool* loc, size_t count, + const GesturesPropBool* init) { + return static_cast<PropertyProvider*>(data)->createBoolArrayProperty(name, loc, count, init); +} + +GesturesProp* createString(void* data, const char* name, const char** loc, const char* const init) { + return static_cast<PropertyProvider*>(data)->createStringProperty(name, loc, init); +} + +GesturesProp* createReal(void* data, const char* name, double* loc, size_t count, + const double* init) { + return static_cast<PropertyProvider*>(data)->createRealArrayProperty(name, loc, count, init); +} + +void registerHandlers(void* data, GesturesProp* prop, void* handlerData, + GesturesPropGetHandler getter, GesturesPropSetHandler setter) { + prop->registerHandlers(handlerData, getter, setter); +} + +void freeProperty(void* data, GesturesProp* prop) { + static_cast<PropertyProvider*>(data)->freeProperty(prop); +} + +} // namespace + +const GesturesPropProvider gesturePropProvider = { + .create_int_fn = createInt, + .create_bool_fn = createBool, + .create_string_fn = createString, + .create_real_fn = createReal, + .register_handlers_fn = registerHandlers, + .free_fn = freeProperty, +}; + +bool PropertyProvider::hasProperty(const std::string name) const { + return mProperties.find(name) != mProperties.end(); +} + +GesturesProp& PropertyProvider::getProperty(const std::string name) { + return mProperties.at(name); +} + +std::string PropertyProvider::dump() const { + std::string dump; + for (const auto& [name, property] : mProperties) { + dump += property.dump() + "\n"; + } + return dump; +} + +GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc, + size_t count, const int* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createBoolArrayProperty(const std::string name, + GesturesPropBool* loc, size_t count, + const GesturesPropBool* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createRealArrayProperty(const std::string name, double* loc, + size_t count, const double* init) { + const auto [it, inserted] = + mProperties.insert(std::pair{name, GesturesProp(name, loc, count, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +GesturesProp* PropertyProvider::createStringProperty(const std::string name, const char** loc, + const char* const init) { + const auto [it, inserted] = mProperties.insert(std::pair{name, GesturesProp(name, loc, init)}); + LOG_ALWAYS_FATAL_IF(!inserted, "Gesture property \"%s\" already exists.", name.c_str()); + return &it->second; +} + +void PropertyProvider::freeProperty(GesturesProp* prop) { + mProperties.erase(prop->getName()); +} + +} // namespace android + +template <typename T> +GesturesProp::GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues) + : mName(name), mCount(count), mDataPointer(dataPointer) { + std::copy_n(initialValues, count, dataPointer); +} + +GesturesProp::GesturesProp(std::string name, const char** dataPointer, + const char* const initialValue) + : mName(name), mCount(1), mDataPointer(dataPointer) { + *(std::get<const char**>(mDataPointer)) = initialValue; +} + +std::string GesturesProp::dump() const { + using android::base::StringPrintf; + std::string type, values; + switch (mDataPointer.index()) { + case 0: + type = "integer"; + values = android::dumpVector(getIntValues()); + break; + case 1: + type = "boolean"; + values = android::dumpVector(getBoolValues()); + break; + case 2: + type = "string"; + values = getStringValue(); + break; + case 3: + type = "real"; + values = android::dumpVector(getRealValues()); + break; + } + std::string typeAndSize = mCount == 1 ? type : std::to_string(mCount) + " " + type + "s"; + return StringPrintf("%s (%s): %s", mName.c_str(), typeAndSize.c_str(), values.c_str()); +} + +void GesturesProp::registerHandlers(void* handlerData, GesturesPropGetHandler getter, + GesturesPropSetHandler setter) { + mHandlerData = handlerData; + mGetter = getter; + mSetter = setter; +} + +std::vector<int> GesturesProp::getIntValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer), + "Attempt to read ints from \"%s\" gesture property.", mName.c_str()); + return getValues<int, int>(std::get<int*>(mDataPointer)); +} + +std::vector<bool> GesturesProp::getBoolValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer), + "Attempt to read bools from \"%s\" gesture property.", mName.c_str()); + return getValues<bool, GesturesPropBool>(std::get<GesturesPropBool*>(mDataPointer)); +} + +std::vector<double> GesturesProp::getRealValues() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer), + "Attempt to read reals from \"%s\" gesture property.", mName.c_str()); + return getValues<double, double>(std::get<double*>(mDataPointer)); +} + +std::string GesturesProp::getStringValue() const { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<const char**>(mDataPointer), + "Attempt to read a string from \"%s\" gesture property.", mName.c_str()); + if (mGetter != nullptr) { + mGetter(mHandlerData); + } + return std::string(*std::get<const char**>(mDataPointer)); +} + +void GesturesProp::setBoolValues(const std::vector<bool>& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer), + "Attempt to write bools to \"%s\" gesture property.", mName.c_str()); + setValues(std::get<GesturesPropBool*>(mDataPointer), values); +} + +void GesturesProp::setIntValues(const std::vector<int>& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer), + "Attempt to write ints to \"%s\" gesture property.", mName.c_str()); + setValues(std::get<int*>(mDataPointer), values); +} + +void GesturesProp::setRealValues(const std::vector<double>& values) { + LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer), + "Attempt to write reals to \"%s\" gesture property.", mName.c_str()); + setValues(std::get<double*>(mDataPointer), values); +} + +template <typename T, typename U> +const std::vector<T> GesturesProp::getValues(U* dataPointer) const { + if (mGetter != nullptr) { + mGetter(mHandlerData); + } + std::vector<T> values; + values.reserve(mCount); + for (size_t i = 0; i < mCount; i++) { + values.push_back(dataPointer[i]); + } + return values; +} + +template <typename T, typename U> +void GesturesProp::setValues(T* dataPointer, const std::vector<U>& values) { + LOG_ALWAYS_FATAL_IF(values.size() != mCount, + "Attempt to write %zu values to \"%s\" gesture property, which holds %zu.", + values.size(), mName.c_str(), mCount); + std::copy(values.begin(), values.end(), dataPointer); + if (mSetter != nullptr) { + mSetter(mHandlerData); + } +} diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h new file mode 100644 index 0000000000..c21260f6c2 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -0,0 +1,100 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <map> +#include <string> +#include <variant> +#include <vector> + +#include "include/gestures.h" + +namespace android { + +// Struct containing functions that wrap PropertyProvider in a C-compatible interface. +extern const GesturesPropProvider gesturePropProvider; + +// Implementation of a gestures library property provider, which provides configuration parameters. +class PropertyProvider { +public: + bool hasProperty(const std::string name) const; + GesturesProp& getProperty(const std::string name); + std::string dump() const; + + // Methods to be called by the gestures library: + GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count, + const int* init); + GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc, + size_t count, const GesturesPropBool* init); + GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count, + const double* init); + GesturesProp* createStringProperty(const std::string name, const char** loc, + const char* const init); + + void freeProperty(GesturesProp* prop); + +private: + std::map<std::string, GesturesProp> mProperties; +}; + +} // namespace android + +// Represents a single gesture property. +// +// Pointers to this struct will be used by the gestures library (though it can never deference +// them). The library's API requires this to be in the top-level namespace. +struct GesturesProp { +public: + template <typename T> + GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues); + GesturesProp(std::string name, const char** dataPointer, const char* const initialValue); + + std::string dump() const; + + std::string getName() const { return mName; } + + size_t getCount() const { return mCount; } + + void registerHandlers(void* handlerData, GesturesPropGetHandler getter, + GesturesPropSetHandler setter); + + std::vector<int> getIntValues() const; + std::vector<bool> getBoolValues() const; + std::vector<double> getRealValues() const; + std::string getStringValue() const; + + void setIntValues(const std::vector<int>& values); + void setBoolValues(const std::vector<bool>& values); + void setRealValues(const std::vector<double>& values); + // Setting string values isn't supported since we don't have a use case yet and the memory + // management adds additional complexity. + +private: + // Two type parameters are required for these methods, rather than one, due to the gestures + // library using its own bool type. + template <typename T, typename U> + const std::vector<T> getValues(U* dataPointer) const; + template <typename T, typename U> + void setValues(T* dataPointer, const std::vector<U>& values); + + std::string mName; + size_t mCount; + std::variant<int*, GesturesPropBool*, const char**, double*> mDataPointer; + void* mHandlerData = nullptr; + GesturesPropGetHandler mGetter = nullptr; + GesturesPropSetHandler mSetter = nullptr; +}; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 58a5c31b78..af40fed51d 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -55,6 +55,7 @@ cc_test { "LatencyTracker_test.cpp", "NotifyArgs_test.cpp", "PreferStylusOverTouch_test.cpp", + "PropertyProvider_test.cpp", "TestInputListener.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp new file mode 100644 index 0000000000..42a6a9f4b9 --- /dev/null +++ b/services/inputflinger/tests/PropertyProvider_test.cpp @@ -0,0 +1,286 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gestures/PropertyProvider.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "include/gestures.h" + +namespace android { + +using testing::ElementsAre; + +class PropertyProviderTest : public testing::Test { +protected: + PropertyProvider mProvider; +}; + +TEST_F(PropertyProviderTest, Int_Create) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {1, 2, 3, 4}; + gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_EQ(prop.getName(), "Some Integers"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Get) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + int* array = static_cast<int*>(handlerData); + array[0] = 1; + array[1] = 2; + array[2] = 3; + array[3] = 4; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ intData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Set) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + int* propertyData; + }; + SetterData setterData = {false, intData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast<SetterData*>(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 1); + EXPECT_EQ(data->propertyData[1], 2); + EXPECT_EQ(data->propertyData[2], 3); + EXPECT_EQ(data->propertyData[3], 4); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + prop.setIntValues({1, 2, 3, 4}); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Bool_Create) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", boolData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_EQ(prop.getName(), "Some Booleans"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(boolData, ElementsAre(true, false, false)); +} + +TEST_F(PropertyProviderTest, Bool_Get) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GesturesPropBool* array = static_cast<GesturesPropBool*>(handlerData); + array[0] = false; + array[1] = true; + array[2] = true; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ boolData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Bool_Set) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + struct SetterData { + bool setterCalled; + GesturesPropBool* propertyData; + }; + SetterData setterData = {false, boolData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast<SetterData*>(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], false); + EXPECT_EQ(data->propertyData[1], true); + EXPECT_EQ(data->propertyData[2], true); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + prop.setBoolValues({false, true, true}); + EXPECT_THAT(boolData, ElementsAre(false, true, true)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Real_Create) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {3.14, 0.7, -5.0}; + gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_EQ(prop.getName(), "Some Reals"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Get) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + double* array = static_cast<double*>(handlerData); + array[0] = 3.14; + array[1] = 0.7; + array[2] = -5.0; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ realData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Set) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + double* propertyData; + }; + SetterData setterData = {false, realData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast<SetterData*>(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 3.14); + EXPECT_EQ(data->propertyData[1], 0.7); + EXPECT_EQ(data->propertyData[2], -5.0); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + prop.setRealValues({3.14, 0.7, -5.0}); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, String_Create) { + const char* str = nullptr; + std::string initialValue = "Foo"; + gesturePropProvider.create_string_fn(&mProvider, "A String", &str, initialValue.c_str()); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getName(), "A String"); + EXPECT_EQ(prop.getCount(), 1u); + EXPECT_STREQ(str, "Foo"); +} + +TEST_F(PropertyProviderTest, String_Get) { + const char* str = nullptr; + std::string initialValue = "Foo"; + GesturesProp* propPtr = gesturePropProvider.create_string_fn(&mProvider, "A String", &str, + initialValue.c_str()); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + struct GetterData { + const char** strPtr; + std::string newValue; // Have to store the new value outside getter so it stays allocated. + }; + GetterData getterData = {&str, "Bar"}; + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GetterData* data = static_cast<GetterData*>(handlerData); + *data->strPtr = data->newValue.c_str(); + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &getterData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getStringValue(), "Bar"); +} + +TEST_F(PropertyProviderTest, Free) { + int intData = 0; + int initialValue = 42; + GesturesProp* propPtr = + gesturePropProvider.create_int_fn(&mProvider, "Foo", &intData, 1, &initialValue); + gesturePropProvider.free_fn(&mProvider, propPtr); + + EXPECT_FALSE(mProvider.hasProperty("Foo")); +} + +} // namespace android |