Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 054ce5cc authored by Yeabkal Wubshit's avatar Yeabkal Wubshit Committed by Android (Google) Code Review
Browse files

Merge "Rotary encoder rotation count telemetry" into main

parents 1b590901 58bda65d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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"
}
+4 −0
Original line number Diff line number Diff line
@@ -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",
+51 −2
Original line number Diff line number Diff line
@@ -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);
    }
+26 −0
Original line number Diff line number Diff line
@@ -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
+149 −0
Original line number Diff line number Diff line
@@ -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