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

Commit e9ae938a authored by Harry Cutts's avatar Harry Cutts Committed by Android (Google) Code Review
Browse files

Merge "CursorInputMapper: share acceleration curves with touchpad" into main

parents d7b39c40 e78184bc
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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 <cstdint>
#include <vector>

namespace android {

/**
 * Describes a section of an acceleration curve as a function which outputs a scaling factor (gain)
 * for the pointer movement, given the speed of the mouse or finger (in mm/s):
 *
 *     gain(input_speed_mm_per_s) = baseGain + reciprocal / input_speed_mm_per_s
 */
struct AccelerationCurveSegment {
    /**
     * The maximum pointer speed at which this segment should apply, in mm/s. The last segment in a
     * curve should always set this to infinity.
     */
    double maxPointerSpeedMmPerS;
    /** The gain for this segment before the reciprocal is taken into account. */
    double baseGain;
    /** The reciprocal part of the formula, which should be divided by the input speed. */
    double reciprocal;
};

/**
 * Creates an acceleration curve for the given pointer sensitivity value. The sensitivity value
 * should be between -7 (for the lowest sensitivity) and 7, inclusive.
 */
std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
        int32_t sensitivity);

} // namespace android
+46 −9
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

#pragma once

#include <vector>

#include <android-base/stringprintf.h>
#include <input/AccelerationCurve.h>
#include <input/Input.h>
#include <input/VelocityTracker.h>
#include <utils/Timers.h>
@@ -86,12 +89,7 @@ struct VelocityControlParameters {
class VelocityControl {
public:
    VelocityControl();

    /* Gets the various parameters. */
    const VelocityControlParameters& getParameters() const;

    /* Sets the various parameters. */
    void setParameters(const VelocityControlParameters& parameters);
    virtual ~VelocityControl() {}

    /* Resets the current movement counters to zero.
     * This has the effect of nullifying any acceleration. */
@@ -101,16 +99,55 @@ public:
     * scaled / accelerated delta based on the current velocity. */
    void move(nsecs_t eventTime, float* deltaX, float* deltaY);

private:
protected:
    virtual void scaleDeltas(float* deltaX, float* deltaY) = 0;

    // If no movements are received within this amount of time,
    // we assume the movement has stopped and reset the movement counters.
    static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms

    VelocityControlParameters mParameters;

    nsecs_t mLastMovementTime;
    float mRawPositionX, mRawPositionY;
    VelocityTracker mVelocityTracker;
};

/**
 * Velocity control using a simple acceleration curve where the acceleration factor increases
 * linearly with movement speed, subject to minimum and maximum values.
 */
class SimpleVelocityControl : public VelocityControl {
public:
    /** Gets the various parameters. */
    const VelocityControlParameters& getParameters() const;

    /** Sets the various parameters. */
    void setParameters(const VelocityControlParameters& parameters);

protected:
    virtual void scaleDeltas(float* deltaX, float* deltaY) override;

private:
    VelocityControlParameters mParameters;
};

/** Velocity control using a curve made up of multiple reciprocal segments. */
class CurvedVelocityControl : public VelocityControl {
public:
    CurvedVelocityControl();

    /** Sets the curve to be used for acceleration. */
    void setCurve(const std::vector<AccelerationCurveSegment>& curve);

    void setAccelerationEnabled(bool enabled);

protected:
    virtual void scaleDeltas(float* deltaX, float* deltaY) override;

private:
    const AccelerationCurveSegment& segmentForSpeed(float speedMmPerS);

    bool mAccelerationEnabled = true;
    std::vector<AccelerationCurveSegment> mCurveSegments;
};

} // namespace android
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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 <input/AccelerationCurve.h>

#include <array>
#include <limits>

#include <log/log_main.h>

#define LOG_TAG "AccelerationCurve"

namespace android {

namespace {

// The last segment must have an infinite maximum speed, so that all speeds are covered.
constexpr std::array<AccelerationCurveSegment, 4> kSegments = {{
        {32.002, 3.19, 0},
        {52.83, 4.79, -51.254},
        {119.124, 7.28, -182.737},
        {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
}};

static_assert(kSegments.back().maxPointerSpeedMmPerS == std::numeric_limits<double>::infinity());

constexpr std::array<double, 15> kSensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
                                                        11, 12, 13, 14, 16, 18, 20};

} // namespace

std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
        int32_t sensitivity) {
    LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value");
    std::vector<AccelerationCurveSegment> output;
    output.reserve(kSegments.size());

    // The curves we want to produce for different sensitivity values are actually the same curve,
    // just scaled in the Y (gain) axis by a sensitivity factor and a couple of constants.
    double commonFactor = 0.64 * kSensitivityFactors[sensitivity + 7] / 10;
    for (AccelerationCurveSegment seg : kSegments) {
        output.push_back(AccelerationCurveSegment{seg.maxPointerSpeedMmPerS,
                                                  commonFactor * seg.baseGain,
                                                  commonFactor * seg.reciprocal});
    }

    return output;
}

} // namespace android
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -175,6 +175,7 @@ cc_library {
    ],
    srcs: [
        "android/os/IInputFlinger.aidl",
        "AccelerationCurve.cpp",
        "Input.cpp",
        "InputDevice.cpp",
        "InputEventLabels.cpp",
+146 −64
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */

#define LOG_TAG "VelocityControl"
//#define LOG_NDEBUG 0

// Log debug messages about acceleration.
static constexpr bool DEBUG_ACCELERATION = false;
@@ -23,6 +22,7 @@ static constexpr bool DEBUG_ACCELERATION = false;
#include <math.h>
#include <limits.h>

#include <android-base/logging.h>
#include <input/VelocityControl.h>
#include <utils/BitSet.h>
#include <utils/Timers.h>
@@ -37,15 +37,6 @@ VelocityControl::VelocityControl() {
    reset();
}

const VelocityControlParameters& VelocityControl::getParameters() const{
    return mParameters;
}

void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
    mParameters = parameters;
    reset();
}

void VelocityControl::reset() {
    mLastMovementTime = LLONG_MIN;
    mRawPositionX = 0;
@@ -54,12 +45,13 @@ void VelocityControl::reset() {
}

void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
    if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
    if ((deltaX == nullptr || *deltaX == 0) && (deltaY == nullptr || *deltaY == 0)) {
        return;
    }
    if (eventTime >= mLastMovementTime + STOP_TIME) {
            if (DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN) {
                ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
        ALOGD_IF(DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN,
                 "VelocityControl: stopped, last movement was %0.3fms ago",
                 (eventTime - mLastMovementTime) * 0.000001f);
            }
        reset();
    }

@@ -70,15 +62,27 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
    if (deltaY) {
        mRawPositionY += *deltaY;
    }
        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X,
                                     mRawPositionX);
        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y,
                                     mRawPositionY);
    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, mRawPositionX);
    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, mRawPositionY);
    scaleDeltas(deltaX, deltaY);
}

// --- SimpleVelocityControl ---

const VelocityControlParameters& SimpleVelocityControl::getParameters() const {
    return mParameters;
}

void SimpleVelocityControl::setParameters(const VelocityControlParameters& parameters) {
    mParameters = parameters;
    reset();
}

void SimpleVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
    float scale = mParameters.scale;
        if (vx && vy) {
    if (vx.has_value() && vy.has_value()) {
        float speed = hypotf(*vx, *vy) * scale;
        if (speed >= mParameters.highThreshold) {
            // Apply full acceleration above the high speed threshold.
@@ -86,33 +90,111 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
        } else if (speed > mParameters.lowThreshold) {
            // Linearly interpolate the acceleration to apply between the low and high
            // speed thresholds.
                scale *= 1 + (speed - mParameters.lowThreshold)
                        / (mParameters.highThreshold - mParameters.lowThreshold)
                        * (mParameters.acceleration - 1);
            scale *= 1 +
                    (speed - mParameters.lowThreshold) /
                            (mParameters.highThreshold - mParameters.lowThreshold) *
                            (mParameters.acceleration - 1);
        }

            if (DEBUG_ACCELERATION) {
                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
        ALOGD_IF(DEBUG_ACCELERATION,
                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
                 "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
                 mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
            }

    } else {
            if (DEBUG_ACCELERATION) {
                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
        ALOGD_IF(DEBUG_ACCELERATION,
                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
                 mParameters.acceleration);
    }
        }

        if (deltaX) {
    if (deltaX != nullptr) {
        *deltaX *= scale;
    }
        if (deltaY) {
    if (deltaY != nullptr) {
        *deltaY *= scale;
    }
}

// --- CurvedVelocityControl ---

namespace {

/**
 * The resolution that we assume a mouse to have, in counts per inch.
 *
 * Mouse resolutions vary wildly, but 800 CPI is probably the most common. There should be enough
 * range in the available sensitivity settings to accommodate users of mice with other resolutions.
 */
constexpr int32_t MOUSE_CPI = 800;

float countsToMm(float counts) {
    return counts / MOUSE_CPI * 25.4;
}

} // namespace

CurvedVelocityControl::CurvedVelocityControl()
      : mCurveSegments(createAccelerationCurveForPointerSensitivity(0)) {}

void CurvedVelocityControl::setCurve(const std::vector<AccelerationCurveSegment>& curve) {
    mCurveSegments = curve;
}

void CurvedVelocityControl::setAccelerationEnabled(bool enabled) {
    mAccelerationEnabled = enabled;
}

void CurvedVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
    if (!mAccelerationEnabled) {
        ALOGD_IF(DEBUG_ACCELERATION, "CurvedVelocityControl: acceleration disabled");
        return;
    }

    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);

    float ratio;
    if (vx.has_value() && vy.has_value()) {
        float vxMmPerS = countsToMm(*vx);
        float vyMmPerS = countsToMm(*vy);
        float speedMmPerS = sqrtf(vxMmPerS * vxMmPerS + vyMmPerS * vyMmPerS);

        const AccelerationCurveSegment& seg = segmentForSpeed(speedMmPerS);
        ratio = seg.baseGain + seg.reciprocal / speedMmPerS;
        ALOGD_IF(DEBUG_ACCELERATION,
                 "CurvedVelocityControl: velocities (%0.3f, %0.3f) → speed %0.3f → ratio %0.3f",
                 vxMmPerS, vyMmPerS, speedMmPerS, ratio);
    } else {
        // We don't have enough data to compute a velocity yet. This happens early in the movement,
        // when the speed is presumably low, so use the base gain of the first segment of the curve.
        // (This would behave oddly for curves with a reciprocal term on the first segment, but we
        // don't have any of those, and they'd be very strange at velocities close to zero anyway.)
        ratio = mCurveSegments[0].baseGain;
        ALOGD_IF(DEBUG_ACCELERATION,
                 "CurvedVelocityControl: unknown velocity, using base gain of first segment (%.3f)",
                 ratio);
    }

    if (deltaX != nullptr) {
        *deltaX *= ratio;
    }
    if (deltaY != nullptr) {
        *deltaY *= ratio;
    }
}

const AccelerationCurveSegment& CurvedVelocityControl::segmentForSpeed(float speedMmPerS) {
    for (const AccelerationCurveSegment& seg : mCurveSegments) {
        if (speedMmPerS <= seg.maxPointerSpeedMmPerS) {
            return seg;
        }
    }
    ALOGE("CurvedVelocityControl: No segment found for speed %.3f; last segment should always have "
          "a max speed of infinity.",
          speedMmPerS);
    return mCurveSegments.back();
}

} // namespace android
Loading