diff options
| author | 2024-09-24 20:22:18 -0700 | |
|---|---|---|
| committer | 2024-09-30 12:42:01 -0700 | |
| commit | 58bda65dfedae9be281a3399fc082c4ee9265fee (patch) | |
| tree | 4c3623f9fc6594b1f63b949547ea947bf90fd4f8 | |
| parent | 0f2f4523ca131cef33dff5d2908640e81684d59f (diff) | |
Rotary encoder rotation count telemetry
Implements a telemetry using the Telemetry Express API to log full
rotations on rotary encoder devices. By default, logs are disabled for
rotations. A rotary input device can change the minimum logged rotation
value via the `rotary_encoder.min_rotations_to_log` IDC property, by
setting it to a positive integer value.
Bug: 370353565
Test: atest RotaryEncoderInputMapperTest
Test: manual with custom logs
Flag: com.android.input.flags.rotary_input_telemetry
Change-Id: I5162b0d343936ac8049c24835cd8e57d44643516
5 files changed, 237 insertions, 2 deletions
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 60fb00e128..701fb43c1f 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -207,3 +207,10 @@ flag { description: "Allow user to enable key repeats or configure timeout before key repeat and key repeat delay rates." bug: "336585002" } + +flag { + name: "rotary_input_telemetry" + namespace: "wear_frameworks" + description: "Enable telemetry for rotary input" + bug: "370353565" +} diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index b76e8c515f..b3cd35c936 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -90,10 +90,14 @@ cc_defaults { "libstatslog", "libstatspull", "libutils", + "libstatssocket", ], static_libs: [ "libchrome-gestures", "libui-types", + "libexpresslog", + "libtextclassifier_hash_static", + "libstatslog_express", ], header_libs: [ "libbatteryservice_headers", diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index b72cc6e060..c633b495e4 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -20,6 +20,8 @@ #include "RotaryEncoderInputMapper.h" +#include <Counter.h> +#include <com_android_input_flags.h> #include <utils/Timers.h> #include <optional> @@ -27,14 +29,26 @@ namespace android { +using android::expresslog::Counter; + +constexpr float kDefaultResolution = 0; constexpr float kDefaultScaleFactor = 1.0f; +constexpr int32_t kDefaultMinRotationsToLog = 3; RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) + : RotaryEncoderInputMapper(deviceContext, readerConfig, + Counter::logIncrement /* telemetryLogCounter */) {} + +RotaryEncoderInputMapper::RotaryEncoderInputMapper( + InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, + std::function<void(const char*, int64_t)> telemetryLogCounter) : InputMapper(deviceContext, readerConfig), mSource(AINPUT_SOURCE_ROTARY_ENCODER), mScalingFactor(kDefaultScaleFactor), - mOrientation(ui::ROTATION_0) {} + mResolution(kDefaultResolution), + mOrientation(ui::ROTATION_0), + mTelemetryLogCounter(telemetryLogCounter) {} RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {} @@ -51,6 +65,7 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { if (!res.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n"); } + mResolution = res.value_or(kDefaultResolution); std::optional<float> scalingFactor = config.getFloat("device.scalingFactor"); if (!scalingFactor.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify scaling factor," @@ -59,7 +74,22 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { } mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor); info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - res.value_or(0.0f) * mScalingFactor); + mResolution * mScalingFactor); + + if (com::android::input::flags::rotary_input_telemetry()) { + mMinRotationsToLog = config.getInt("rotary_encoder.min_rotations_to_log"); + if (!mMinRotationsToLog.has_value()) { + ALOGI("Rotary Encoder device configuration file didn't specify min log rotation."); + } else if (*mMinRotationsToLog <= 0) { + ALOGE("Rotary Encoder device configuration specified non-positive min log rotation " + ": %d. Telemetry logging of rotations disabled.", + *mMinRotationsToLog); + mMinRotationsToLog = {}; + } else { + ALOGD("Rotary Encoder telemetry enabled. mMinRotationsToLog=%d", + *mMinRotationsToLog); + } + } } } @@ -121,10 +151,29 @@ std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent& rawEvent return out; } +void RotaryEncoderInputMapper::logScroll(float scroll) { + if (mResolution <= 0 || !mMinRotationsToLog) return; + + mUnloggedScrolls += fabs(scroll); + + // unitsPerRotation = (2 * PI * radians) * (units per radian (i.e. resolution)) + const float unitsPerRotation = 2 * M_PI * mResolution; + const float scrollsPerMinRotationsToLog = *mMinRotationsToLog * unitsPerRotation; + const int32_t numMinRotationsToLog = + static_cast<int32_t>(mUnloggedScrolls / scrollsPerMinRotationsToLog); + mUnloggedScrolls = std::fmod(mUnloggedScrolls, scrollsPerMinRotationsToLog); + if (numMinRotationsToLog) { + mTelemetryLogCounter("input.value_rotary_input_device_full_rotation_count", + numMinRotationsToLog * (*mMinRotationsToLog)); + } +} + std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { std::list<NotifyArgs> out; float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel(); + logScroll(scroll); + if (mSlopController) { scroll = mSlopController->consumeEvent(when, scroll); } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 7e804150b1..d74ced180e 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -46,13 +46,39 @@ private: int32_t mSource; float mScalingFactor; + /** Units per rotation, provided via the `device.res` IDC property. */ + float mResolution; ui::Rotation mOrientation; + /** + * The minimum number of rotations to log for telemetry. + * Provided via `rotary_encoder.min_rotations_to_log` IDC property. If no value is provided in + * the IDC file, or if a non-positive value is provided, the telemetry will be disabled, and + * this value is set to the empty optional. + */ + std::optional<int32_t> mMinRotationsToLog; + /** + * A function to log count for telemetry. + * The char* is the logging key, and the int64_t is the value to log. + * Abstracting the actual logging APIs via this function is helpful for simple unit testing. + */ + std::function<void(const char*, int64_t)> mTelemetryLogCounter; ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID; std::unique_ptr<SlopController> mSlopController; + /** Amount of raw scrolls (pre-slop) not yet logged for telemetry. */ + float mUnloggedScrolls = 0; + explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig); + + /** This is a test constructor that allows injecting the expresslog Counter logic. */ + RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + std::function<void(const char*, int64_t)> expressLogCounter); [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime); + + /** Logs a given amount of scroll for telemetry. */ + void logScroll(float scroll); }; } // namespace android diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp index 6607bc7972..486d893944 100644 --- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp +++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp @@ -23,6 +23,8 @@ #include <android-base/logging.h> #include <android_companion_virtualdevice_flags.h> +#include <com_android_input_flags.h> +#include <flag_macros.h> #include <gtest/gtest.h> #include <input/DisplayViewport.h> #include <linux/input-event-codes.h> @@ -100,6 +102,15 @@ protected: EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) .WillRepeatedly(Return(false)); } + + std::map<const char*, int64_t> mTelemetryLogCounts; + + /** + * A fake function for telemetry logging. + * Records the log counts in the `mTelemetryLogCounts` map. + */ + std::function<void(const char*, int64_t)> mTelemetryLogCounter = + [this](const char* key, int64_t value) { mTelemetryLogCounts[key] += value; }; }; TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) { @@ -187,4 +198,142 @@ TEST_F(RotaryEncoderInputMapperTest, HighResScrollIgnoresRegularScroll) { WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f))))); } +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotaryInputTelemetryFlagOff_NoRotationLogging, + REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + mPropertyMap.addProperty("device.res", "3"); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 70); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); +} + +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroResolution_NoRotationLogging, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + mPropertyMap.addProperty("device.res", "-3"); + mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2"); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); +} + +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NegativeMinLogRotation_NoRotationLogging, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + mPropertyMap.addProperty("device.res", "3"); + mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "-2"); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); +} + +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroMinLogRotation_NoRotationLogging, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + mPropertyMap.addProperty("device.res", "3"); + mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "0"); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); +} + +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NoMinLogRotation_NoRotationLogging, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation. + mPropertyMap.addProperty("device.res", "3"); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); +} + +TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotationLogging, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + rotary_input_telemetry))) { + // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation. + // Multiples of `unitsPerRoation`, to easily follow the assertions below. + // [18.85, 37.7, 56.55, 75.4, 94.25, 113.1, 131.95, 150.8] + mPropertyMap.addProperty("device.res", "3"); + mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2"); + + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration, + mTelemetryLogCounter); + InputDeviceInfo info; + mMapper->populateDeviceInfo(info); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 15); // total scroll = 15 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); + + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 13); // total scroll = 28 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + // Expect 0 since `min_rotations_to_log` = 2, and total scroll 28 only has 1 rotation. + ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"), + mTelemetryLogCounts.end()); + + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 10); // total scroll = 38 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + // Total scroll includes >= `min_rotations_to_log` (2), expect log. + ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2); + + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -22); // total scroll = 60 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + // Expect no additional telemetry. Total rotation is 3, and total unlogged rotation is 1, which + // is less than `min_rotations_to_log`. + ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2); + + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -16); // total scroll = 76 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + // Total unlogged rotation >= `min_rotations_to_log` (2), so expect 2 more logged rotation. + ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 4); + + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -76); // total scroll = 152 + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + // Total unlogged scroll >= 4*`min_rotations_to_log`. Expect *all* unlogged rotations to be + // logged, even if that's more than multiple of `min_rotations_to_log`. + ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 8); +} + } // namespace android
\ No newline at end of file |