diff options
19 files changed, 331 insertions, 510 deletions
diff --git a/data/etc/Android.bp b/data/etc/Android.bp index bdd5172e60..a737bd3fb7 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -167,6 +167,12 @@ prebuilt_etc { } prebuilt_etc { + name: "android.hardware.telephony.satellite.prebuilt.xml", + src: "android.hardware.telephony.satellite.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { name: "android.hardware.usb.accessory.prebuilt.xml", src: "android.hardware.usb.accessory.xml", defaults: ["frameworks_native_data_etc_defaults"], diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml new file mode 100644 index 0000000000..5966cba277 --- /dev/null +++ b/data/etc/android.hardware.telephony.satellite.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> + +<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. --> +<permissions> + <feature name="android.hardware.telephony.satellite" /> +</permissions> diff --git a/include/android/keycodes.h b/include/android/keycodes.h index d4ba321057..f8fb256fae 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -831,6 +831,14 @@ enum { AKEYCODE_STYLUS_BUTTON_TAIL = 311, /** Key to open recent apps (a.k.a. Overview) */ AKEYCODE_RECENT_APPS = 312, + /** User customizable key #1. */ + AKEYCODE_MACRO_1 = 313, + /** User customizable key #2. */ + AKEYCODE_MACRO_2 = 314, + /** User customizable key #3. */ + AKEYCODE_MACRO_3 = 315, + /** User customizable key #4. */ + AKEYCODE_MACRO_4 = 316, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h index 67984b7c80..5e9972eef8 100644 --- a/include/input/RingBuffer.h +++ b/include/input/RingBuffer.h @@ -24,7 +24,6 @@ #include <type_traits> #include <utility> -#include <android-base/logging.h> #include <android-base/stringprintf.h> namespace android { @@ -272,15 +271,16 @@ private: // Converts the index of an element in [0, size()] to its corresponding index in mBuffer. size_type bufferIndex(size_type elementIndex) const { - CHECK_LE(elementIndex, size()); + if (elementIndex > size()) { + abort(); + } size_type index = mBegin + elementIndex; if (index >= capacity()) { index -= capacity(); } - CHECK_LT(index, capacity()) - << android::base::StringPrintf("Invalid index calculated for element (%zu) " - "in buffer of size %zu", - elementIndex, size()); + if (index >= capacity()) { + abort(); + } return index; } diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index da97c3e855..b58feac444 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -17,6 +17,7 @@ #pragma once #include <input/Input.h> +#include <input/RingBuffer.h> #include <utils/BitSet.h> #include <utils/Timers.h> #include <map> @@ -31,6 +32,8 @@ class VelocityTrackerStrategy; */ class VelocityTracker { public: + static const size_t MAX_DEGREE = 4; + enum class Strategy : int32_t { DEFAULT = -1, MIN = 0, @@ -47,23 +50,6 @@ public: MAX = LEGACY, }; - struct Estimator { - static const size_t MAX_DEGREE = 4; - - // Estimator time base. - nsecs_t time = 0; - - // Polynomial coefficients describing motion. - std::array<float, MAX_DEGREE + 1> coeff{}; - - // Polynomial degree (number of coefficients), or zero if no information is - // available. - uint32_t degree = 0; - - // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit). - float confidence = 0; - }; - /* * Contains all available velocity data from a VelocityTracker. */ @@ -124,11 +110,6 @@ public: // [-maxVelocity, maxVelocity], inclusive. ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity); - // Gets an estimator for the recent movements of the specified pointer id for the given axis. - // Returns false and clears the estimator if there is no information available - // about the pointer. - std::optional<Estimator> getEstimator(int32_t axis, int32_t pointerId) const; - // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); } @@ -169,14 +150,48 @@ public: virtual void clearPointer(int32_t pointerId) = 0; virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0; - virtual std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const = 0; + virtual std::optional<float> getVelocity(int32_t pointerId) const = 0; }; +/** + * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data + * points when getting velocity. + */ +class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy { +public: + AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos, bool maintainHorizonDuringAdd); + + void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; + void clearPointer(int32_t pointerId) override; + +protected: + struct Movement { + nsecs_t eventTime; + float position; + }; + + // Number of samples to keep. + // If different strategies would like to maintain different history size, we can make this a + // protected const field. + static constexpr uint32_t HISTORY_SIZE = 20; + + /** + * Duration, in nanoseconds, since the latest movement where a movement may be considered for + * velocity calculation. + */ + const nsecs_t mHorizonNanos; + /** + * If true, data points outside of horizon (see `mHorizonNanos`) will be cleared after each + * addition of a new movement. + */ + const bool mMaintainHorizonDuringAdd; + std::map<int32_t /*pointerId*/, RingBuffer<Movement>> mMovements; +}; /* * Velocity tracker algorithm based on least-squares linear regression. */ -class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { +class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: enum class Weighting { // No weights applied. All data points are equally reliable. @@ -193,13 +208,11 @@ public: RECENT, }; - // Degree must be no greater than Estimator::MAX_DEGREE. + // Degree must be no greater than VelocityTracker::MAX_DEGREE. LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE); ~LeastSquaresVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Sample horizon. @@ -207,23 +220,19 @@ private: // changes in direction. static const nsecs_t HORIZON = 100 * 1000000; // 100 ms - // Number of samples to keep. - static const uint32_t HISTORY_SIZE = 20; - - struct Movement { - nsecs_t eventTime; - float position; - }; - float chooseWeight(int32_t pointerId, uint32_t index) const; + /** + * An optimized least-squares solver for degree 2 and no weight (i.e. `Weighting.NONE`). + * The provided container of movements shall NOT be empty, and shall have the movements in + * chronological order. + */ + std::optional<float> solveUnweightedLeastSquaresDeg2( + const RingBuffer<Movement>& movements) const; const uint32_t mDegree; const Weighting mWeighting; - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; - /* * Velocity tracker algorithm that uses an IIR filter. */ @@ -235,7 +244,7 @@ public: void clearPointer(int32_t pointerId) override; void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Current state estimate for a particular pointer. @@ -252,49 +261,33 @@ private: void initState(State& state, nsecs_t eventTime, float pos) const; void updateState(State& state, nsecs_t eventTime, float pos) const; - void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; /* * Velocity tracker strategy used prior to ICS. */ -class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy { +class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: LegacyVelocityTrackerStrategy(); ~LegacyVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Oldest sample to consider when calculating the velocity. static const nsecs_t HORIZON = 200 * 1000000; // 100 ms - // Number of samples to keep. - static const uint32_t HISTORY_SIZE = 20; - // The minimum duration between samples when estimating velocity. static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms - - struct Movement { - nsecs_t eventTime; - float position; - }; - - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; -class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy { +class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy { public: ImpulseVelocityTrackerStrategy(bool deltaValues); ~ImpulseVelocityTrackerStrategy() override; - void clearPointer(int32_t pointerId) override; - void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override; - std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override; + std::optional<float> getVelocity(int32_t pointerId) const override; private: // Sample horizon. @@ -302,21 +295,10 @@ private: // changes in direction. static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms - // Number of samples to keep. - static constexpr size_t HISTORY_SIZE = 20; - - struct Movement { - nsecs_t eventTime; - float position; - }; - // Whether or not the input movement values for the strategy come in the form of delta values. // If the input values are not deltas, the strategy needs to calculate deltas as part of its // velocity calculation. const bool mDeltaValues; - - std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex; - std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements; }; } // namespace android diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index eaf3b5e791..d50c5f846e 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,6 +17,8 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H +#include <stdint.h> + __BEGIN_DECLS /** @@ -27,7 +29,7 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager); /** * Hints for the session used to signal upcoming changes in the mode or workload. */ -enum SessionHint { +enum SessionHint: int32_t { /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the @@ -61,7 +63,7 @@ enum SessionHint { * @return 0 on success * EPIPE if communication with the system service has failed. */ -int APerformanceHint_sendHint(void* session, int hint); +int APerformanceHint_sendHint(void* session, SessionHint hint); /** * Return the list of thread ids, this API should only be used for testing only. diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h index 1dddeba616..bf354e7bb4 100644 --- a/libs/gui/include/gui/JankInfo.h +++ b/libs/gui/include/gui/JankInfo.h @@ -46,6 +46,8 @@ enum JankType { // where the previous frame was presented in the current frame's expected vsync. This pushes the // current frame to the next vsync. The behavior is similar to BufferStuffing. SurfaceFlingerStuffing = 0x100, + // Frame was dropped, as a newer frame was ready and replaced this frame. + Dropped = 0x200, }; } // namespace android diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 4a19227694..f99a7d640e 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -343,7 +343,11 @@ namespace android { DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_TAIL), \ - DEFINE_KEYCODE(RECENT_APPS) + DEFINE_KEYCODE(RECENT_APPS), \ + DEFINE_KEYCODE(MACRO_1), \ + DEFINE_KEYCODE(MACRO_2), \ + DEFINE_KEYCODE(MACRO_3), \ + DEFINE_KEYCODE(MACRO_4) // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index b4151c6ea1..0d4213c745 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -25,9 +25,9 @@ #include <string> #include <vector> +#include <android-base/logging.h> #include <android-base/strings.h> #include <android/input.h> -#include <log/log.h> #include <attestation/HmacKeyManager.h> #include <input/TfLiteMotionPredictor.h> diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 8551e5fa1c..87c7768f25 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -22,7 +22,6 @@ #include <math.h> #include <optional> -#include <android-base/stringprintf.h> #include <input/PrintTools.h> #include <input/VelocityTracker.h> #include <utils/BitSet.h> @@ -56,6 +55,9 @@ const bool DEBUG_IMPULSE = // Nanoseconds per milliseconds. static const nsecs_t NANOS_PER_MS = 1000000; +// Seconds per nanosecond. +static const float SECONDS_PER_NANO = 1E-9; + // All axes supported for velocity tracking, mapped to their default strategies. // Although other strategies are available for testing and comparison purposes, // the default strategy is the one that applications will actually use. Be very careful @@ -268,12 +270,8 @@ void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t ", activePointerId=%s", eventTime, pointerId, toString(mActivePointerId).c_str()); - std::optional<Estimator> estimator = getEstimator(axis, pointerId); - ALOGD(" %d: axis=%d, position=%0.3f, " - "estimator (degree=%d, coeff=%s, confidence=%f)", - pointerId, axis, position, int((*estimator).degree), - vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(), - (*estimator).confidence); + ALOGD(" %d: axis=%d, position=%0.3f, velocity=%s", pointerId, axis, position, + toString(getVelocity(axis, pointerId)).c_str()); } } @@ -349,9 +347,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { } std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const { - std::optional<Estimator> estimator = getEstimator(axis, pointerId); - if (estimator && (*estimator).degree >= 1) { - return (*estimator).coeff[1]; + const auto& it = mConfiguredStrategies.find(axis); + if (it != mConfiguredStrategies.end()) { + return it->second->getVelocity(pointerId); } return {}; } @@ -374,57 +372,53 @@ VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t u return computedVelocity; } -std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis, - int32_t pointerId) const { - const auto& it = mConfiguredStrategies.find(axis); - if (it == mConfiguredStrategies.end()) { - return std::nullopt; - } - return it->second->getEstimator(pointerId); -} - -// --- LeastSquaresVelocityTrackerStrategy --- +AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy( + nsecs_t horizonNanos, bool maintainHorizonDuringAdd) + : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {} -LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, - Weighting weighting) - : mDegree(degree), mWeighting(weighting) {} - -LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { -} - -void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); +void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) { mMovements.erase(pointerId); } -void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, +void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { + auto [ringBufferIt, _] = mMovements.try_emplace(pointerId, HISTORY_SIZE); + RingBuffer<Movement>& movements = ringBufferIt->second; + const size_t size = movements.size(); + + if (size != 0 && movements[size - 1].eventTime == eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. + // for this time (i.e. pop out the last element, and insert the updated movement). // We only compare against the last value, as it is likely that addMovement is called // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; + movements.popBack(); } - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; + movements.pushBack({eventTime, position}); + + // Clear movements that do not fall within `mHorizonNanos` of the latest movement. + // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE), + // we can consider making this step binary-search based, which will give us some improvement. + if (mMaintainHorizonDuringAdd) { + while (eventTime - movements[0].eventTime > mHorizonNanos) { + movements.popFront(); + } + } } +// --- LeastSquaresVelocityTrackerStrategy --- + +LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, + Weighting weighting) + : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/, + true /*maintainHorizonDuringAdd*/), + mDegree(degree), + mWeighting(weighting) {} + +LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} + /** * Solves a linear least squares problem to obtain a N degree polynomial that fits * the specified input data as nearly as possible. @@ -474,10 +468,9 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares * http://en.wikipedia.org/wiki/Gram-Schmidt */ -static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y, - const std::vector<float>& w, uint32_t n, - std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB, - float* outDet) { +static std::optional<float> solveLeastSquares(const std::vector<float>& x, + const std::vector<float>& y, + const std::vector<float>& w, uint32_t n) { const size_t m = x.size(); ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), @@ -515,7 +508,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo if (norm < 0.000001f) { // vectors are linearly dependent or zero so no solution ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm); - return false; + return {}; } float invNorm = 1.0f / norm; @@ -549,6 +542,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo for (uint32_t h = 0; h < m; h++) { wy[h] = y[h] * w[h]; } + std::array<float, VelocityTracker::MAX_DEGREE + 1> outB; for (uint32_t i = n; i != 0; ) { i--; outB[i] = vectorDot(&q[i][0], wy, m); @@ -570,42 +564,46 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo } ymean /= m; - float sserr = 0; - float sstot = 0; - for (uint32_t h = 0; h < m; h++) { - float err = y[h] - outB[0]; - float term = 1; - for (uint32_t i = 1; i < n; i++) { - term *= x[h]; - err -= term * outB[i]; + if (DEBUG_STRATEGY) { + float sserr = 0; + float sstot = 0; + for (uint32_t h = 0; h < m; h++) { + float err = y[h] - outB[0]; + float term = 1; + for (uint32_t i = 1; i < n; i++) { + term *= x[h]; + err -= term * outB[i]; + } + sserr += w[h] * w[h] * err * err; + float var = y[h] - ymean; + sstot += w[h] * w[h] * var * var; } - sserr += w[h] * w[h] * err * err; - float var = y[h] - ymean; - sstot += w[h] * w[h] * var * var; + ALOGD(" - sserr=%f", sserr); + ALOGD(" - sstot=%f", sstot); } - *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; - ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); - ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); - ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); - - return true; + return outB[1]; } /* * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to * the default implementation */ -static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2( - const std::vector<float>& x, const std::vector<float>& y) { - const size_t count = x.size(); - LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes"); - // Solving y = a*x^2 + b*x + c +std::optional<float> LeastSquaresVelocityTrackerStrategy::solveUnweightedLeastSquaresDeg2( + const RingBuffer<Movement>& movements) const { + // Solving y = a*x^2 + b*x + c, where + // - "x" is age (i.e. duration since latest movement) of the movemnets + // - "y" is positions of the movements. float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0; + const size_t count = movements.size(); + const Movement& newestMovement = movements[count - 1]; for (size_t i = 0; i < count; i++) { - float xi = x[i]; - float yi = y[i]; + const Movement& movement = movements[i]; + nsecs_t age = newestMovement.eventTime - movement.eventTime; + float xi = -age * SECONDS_PER_NANO; + float yi = movement.position; + float xi2 = xi*xi; float xi3 = xi2*xi; float xi4 = xi3*xi; @@ -632,124 +630,68 @@ static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2( ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2); return std::nullopt; } - // Compute a - float numerator = Sx2y*Sxx - Sxy*Sxx2; - float a = numerator / denominator; - - // Compute b - numerator = Sxy*Sx2x2 - Sx2y*Sxx2; - float b = numerator / denominator; - // Compute c - float c = syi/count - b * sxi/count - a * sxi2/count; - - return std::make_optional(std::array<float, 3>({c, b, a})); + return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator; } -std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } - // Iterate over movement samples in reverse time order and collect samples. - std::vector<float> positions; - std::vector<float> w; - std::vector<float> time; - - uint32_t index = mIndex.at(pointerId); - const Movement& newestMovement = movementIt->second[index]; - do { - const Movement& movement = movementIt->second[index]; - - nsecs_t age = newestMovement.eventTime - movement.eventTime; - if (age > HORIZON) { - break; - } - if (movement.eventTime == 0 && index != 0) { - // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's - // possible that not all entries are valid. We use a time=0 as a signal for those - // uninitialized values. If we encounter a time of 0 in a position - // that's > 0, it means that we hit the block where the data wasn't initialized. - // We still don't know whether the value at index=0, with eventTime=0 is valid. - // However, that's only possible when the value is by itself. So there's no hard in - // processing it anyways, since the velocity for a single point is zero, and this - // situation will only be encountered in artificial circumstances (in tests). - // In practice, time will never be 0. - break; - } - positions.push_back(movement.position); - w.push_back(chooseWeight(pointerId, index)); - time.push_back(-age * 0.000000001f); - index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (positions.size() < HISTORY_SIZE); - const size_t m = positions.size(); - if (m == 0) { + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { return std::nullopt; // no data } - // Calculate a least squares polynomial fit. uint32_t degree = mDegree; - if (degree > m - 1) { - degree = m - 1; + if (degree > size - 1) { + degree = size - 1; + } + + if (degree <= 0) { + return std::nullopt; } if (degree == 2 && mWeighting == Weighting::NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional<std::array<float, 3>> coeff = - solveUnweightedLeastSquaresDeg2(time, positions); - if (coeff) { - VelocityTracker::Estimator estimator; - estimator.time = newestMovement.eventTime; - estimator.degree = 2; - estimator.confidence = 1; - for (size_t i = 0; i <= estimator.degree; i++) { - estimator.coeff[i] = (*coeff)[i]; - } - return estimator; - } - } else if (degree >= 1) { - // General case for an Nth degree polynomial fit - float det; - uint32_t n = degree + 1; - VelocityTracker::Estimator estimator; - if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) { - estimator.time = newestMovement.eventTime; - estimator.degree = degree; - estimator.confidence = det; - - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", - int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(), - estimator.confidence); - - return estimator; - } + return solveUnweightedLeastSquaresDeg2(movements); + } + + // Iterate over movement samples in reverse time order and collect samples. + std::vector<float> positions; + std::vector<float> w; + std::vector<float> time; + + const Movement& newestMovement = movements[size - 1]; + for (ssize_t i = size - 1; i >= 0; i--) { + const Movement& movement = movements[i]; + nsecs_t age = newestMovement.eventTime - movement.eventTime; + positions.push_back(movement.position); + w.push_back(chooseWeight(pointerId, i)); + time.push_back(-age * 0.000000001f); } - // No velocity data available for this pointer, but we do have its current position. - VelocityTracker::Estimator estimator; - estimator.coeff[0] = positions[0]; - estimator.time = newestMovement.eventTime; - estimator.degree = 0; - estimator.confidence = 1; - return estimator; + // General case for an Nth degree polynomial fit + return solveLeastSquares(time, positions, w, degree + 1); } float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const { - const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId); + const RingBuffer<Movement>& movements = mMovements.at(pointerId); + const size_t size = movements.size(); switch (mWeighting) { case Weighting::DELTA: { // Weight points based on how much time elapsed between them and the next // point so that points that "cover" a shorter time span are weighed less. // delta 0ms: 0.5 // delta 10ms: 1.0 - if (index == mIndex.at(pointerId)) { + if (index == size - 1) { return 1.0f; } - uint32_t nextIndex = (index + 1) % HISTORY_SIZE; float deltaMillis = - (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f; + (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f; if (deltaMillis < 0) { return 0.5f; } @@ -766,8 +708,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3 // age 50ms: 1.0 // age 60ms: 0.5 float ageMillis = - (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * - 0.000001f; + (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f; if (ageMillis < 0) { return 0.5f; } @@ -789,8 +730,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3 // age 50ms: 1.0 // age 100ms: 0.5 float ageMillis = - (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) * - 0.000001f; + (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f; if (ageMillis < 50) { return 1.0f; } @@ -830,13 +770,9 @@ void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t mPointerIdBits.markBit(pointerId); } -std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { if (mPointerIdBits.hasBit(pointerId)) { - const State& state = mPointerState[pointerId]; - VelocityTracker::Estimator estimator; - populateEstimator(state, &estimator); - return estimator; + return mPointerState[pointerId].vel; } return std::nullopt; @@ -886,77 +822,39 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t event state.pos = pos; } -void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->time = state.updateTime; - outEstimator->confidence = 1.0f; - outEstimator->degree = state.degree; - outEstimator->coeff[0] = state.pos; - outEstimator->coeff[1] = state.vel; - outEstimator->coeff[2] = state.accel / 2; -} - - // --- LegacyVelocityTrackerStrategy --- -LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {} +LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() + : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/, + false /*maintainHorizonDuringAdd*/) {} LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { } -void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); - mMovements.erase(pointerId); -} - -void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, - float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { - // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates - // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include - // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. - // We only compare against the last value, as it is likely that addMovement is called - // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; - } - - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; -} - -std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } - const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)]; + + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { + return std::nullopt; // no data + } + + const Movement& newestMovement = movements[size - 1]; // Find the oldest sample that contains the pointer and that is not older than HORIZON. nsecs_t minTime = newestMovement.eventTime - HORIZON; - uint32_t oldestIndex = mIndex.at(pointerId); - uint32_t numTouches = 1; - do { - uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; - const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex]; + uint32_t oldestIndex = size - 1; + for (ssize_t i = size - 1; i >= 0; i--) { + const Movement& nextOldestMovement = movements[i]; if (nextOldestMovement.eventTime < minTime) { break; } - oldestIndex = nextOldestIndex; - } while (++numTouches < HISTORY_SIZE); + oldestIndex = i; + } // Calculate an exponentially weighted moving average of the velocity estimate // at different points in time measured relative to the oldest sample. @@ -970,17 +868,13 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. float accumV = 0; - uint32_t index = oldestIndex; uint32_t samplesUsed = 0; - const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex]; + const Movement& oldestMovement = movements[oldestIndex]; float oldestPosition = oldestMovement.position; nsecs_t lastDuration = 0; - while (numTouches-- > 1) { - if (++index == HISTORY_SIZE) { - index = 0; - } - const Movement& movement = mMovements.at(pointerId)[index]; + for (size_t i = oldestIndex; i < size; i++) { + const Movement& movement = movements[i]; nsecs_t duration = movement.eventTime - oldestMovement.eventTime; // If the duration between samples is small, we may significantly overestimate @@ -996,62 +890,22 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti } } - // Report velocity. - float newestPosition = newestMovement.position; - VelocityTracker::Estimator estimator; - estimator.time = newestMovement.eventTime; - estimator.confidence = 1; - estimator.coeff[0] = newestPosition; if (samplesUsed) { - estimator.coeff[1] = accumV; - estimator.degree = 1; - } else { - estimator.degree = 0; + return accumV; } - return estimator; + return std::nullopt; } // --- ImpulseVelocityTrackerStrategy --- ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues) - : mDeltaValues(deltaValues) {} + : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/, + true /*maintainHorizonDuringAdd*/), + mDeltaValues(deltaValues) {} ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { } -void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) { - mIndex.erase(pointerId); - mMovements.erase(pointerId); -} - -void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId, - float position) { - // If data for this pointer already exists, we have a valid entry at the position of - // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index - // to the next position in the circular buffer and write the new Movement there. Otherwise, - // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements - // for this pointer and write to the first position. - auto [movementIt, inserted] = mMovements.insert({pointerId, {}}); - auto [indexIt, _] = mIndex.insert({pointerId, 0}); - size_t& index = indexIt->second; - if (!inserted && movementIt->second[index].eventTime != eventTime) { - // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates - // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include - // the new pointer. If the eventtimes for both events are identical, just update the data - // for this time. - // We only compare against the last value, as it is likely that addMovement is called - // in chronological order as events occur. - index++; - } - if (index == HISTORY_SIZE) { - index = 0; - } - - Movement& movement = movementIt->second[index]; - movement.eventTime = eventTime; - movement.position = position; -} - /** * Calculate the total impulse provided to the screen and the resulting velocity. * @@ -1126,112 +980,44 @@ static float kineticEnergyToVelocity(float work) { return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2; } -static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count, - bool deltaValues) { - // The input should be in reversed time order (most recent sample at index i=0) - // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function - static constexpr float SECONDS_PER_NANO = 1E-9; - - if (count < 2) { - return 0; // if 0 or 1 points, velocity is zero - } - if (t[1] > t[0]) { // Algorithm will still work, but not perfectly - ALOGE("Samples provided to calculateImpulseVelocity in the wrong order"); - } - - // If the data values are delta values, we do not have to calculate deltas here. - // We can use the delta values directly, along with the calculated time deltas. - // Since the data value input is in reversed time order: - // [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1]) - // [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1]) - // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4]. - // Since the input is in reversed time order, the input values for this function would be - // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively. - // - // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1} - // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4} - - if (count == 2) { // if 2 points, basic linear calculation - if (t[1] == t[0]) { - ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]); - return 0; - } - const float deltaX = deltaValues ? -x[0] : x[1] - x[0]; - return deltaX / (SECONDS_PER_NANO * (t[1] - t[0])); - } - // Guaranteed to have at least 3 points here - float work = 0; - for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time - if (t[i] == t[i-1]) { - ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]); - continue; - } - float vprev = kineticEnergyToVelocity(work); // v[i-1] - const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1]; - float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] - work += (vcurr - vprev) * fabsf(vcurr); - if (i == count - 1) { - work *= 0.5; // initial condition, case 2) above - } - } - return kineticEnergyToVelocity(work); -} - -std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator( - int32_t pointerId) const { +std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const { const auto movementIt = mMovements.find(pointerId); if (movementIt == mMovements.end()) { return std::nullopt; // no data } - // Iterate over movement samples in reverse time order and collect samples. - float positions[HISTORY_SIZE]; - nsecs_t time[HISTORY_SIZE]; - size_t m = 0; // number of points that will be used for fitting - size_t index = mIndex.at(pointerId); - const Movement& newestMovement = movementIt->second[index]; - do { - const Movement& movement = movementIt->second[index]; + const RingBuffer<Movement>& movements = movementIt->second; + const size_t size = movements.size(); + if (size == 0) { + return std::nullopt; // no data + } - nsecs_t age = newestMovement.eventTime - movement.eventTime; - if (age > HORIZON) { - break; - } - if (movement.eventTime == 0 && index != 0) { - // All eventTime's are initialized to 0. If we encounter a time of 0 in a position - // that's >0, it means that we hit the block where the data wasn't initialized. - // It's also possible that the sample at 0 would be invalid, but there's no harm in - // processing it, since it would be just a single point, and will only be encountered - // in artificial circumstances (in tests). - break; - } + float work = 0; + for (size_t i = 0; i < size - 1; i++) { + const Movement& mvt = movements[i]; + const Movement& nextMvt = movements[i + 1]; - positions[m] = movement.position; - time[m] = movement.eventTime; - index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (++m < HISTORY_SIZE); + float vprev = kineticEnergyToVelocity(work); + float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position; + float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime)); + work += (vcurr - vprev) * fabsf(vcurr); - if (m == 0) { - return std::nullopt; // no data + if (i == 0) { + work *= 0.5; // initial condition, case 2) above + } } - VelocityTracker::Estimator estimator; - estimator.coeff[0] = 0; - estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues); - estimator.coeff[2] = 0; - - estimator.time = newestMovement.eventTime; - estimator.degree = 2; // similar results to 2nd degree fit - estimator.confidence = 1; - ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]); + const float velocity = kineticEnergyToVelocity(work); + ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons. // X axis chosen arbitrarily for velocity comparisons. VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); - for (ssize_t i = m - 1; i >= 0; i--) { - lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]); + for (size_t i = 0; i < size; i++) { + const Movement& mvt = movements[i]; + lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position); } std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId); if (v) { @@ -1240,7 +1026,7 @@ std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEst ALOGD("lsq2 velocity: could not compute velocity"); } } - return estimator; + return velocity; } } // namespace android diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 027757973b..2a424afeee 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -42,8 +42,8 @@ constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually define // here EV = expected value, tol = VELOCITY_TOLERANCE constexpr float VELOCITY_TOLERANCE = 0.2; -// estimate coefficients must be within 0.001% of the target value -constexpr float COEFFICIENT_TOLERANCE = 0.00001; +// quadratic velocity must be within 0.001% of the target value +constexpr float QUADRATIC_VELOCITY_TOLERANCE = 0.00001; // --- VelocityTrackerTest --- class VelocityTrackerTest : public testing::Test { }; @@ -76,10 +76,6 @@ static void checkVelocity(std::optional<float> Vactual, std::optional<float> Vta } } -static void checkCoefficient(float actual, float target) { - EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE); -} - struct Position { float x; float y; @@ -284,21 +280,20 @@ static void computeAndCheckAxisScrollVelocity( checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity); } -static void computeAndCheckQuadraticEstimate(const std::vector<PlanarMotionEventEntry>& motions, - const std::array<float, 3>& coefficients) { +static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions, + float velocity) { VelocityTracker vt(VelocityTracker::Strategy::LSQ2); std::vector<MotionEvent> events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0); - std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0); - EXPECT_TRUE(estimatorX); - EXPECT_TRUE(estimatorY); - for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient((*estimatorX).coeff[i], coefficients[i]); - checkCoefficient((*estimatorY).coeff[i], coefficients[i]); - } + std::optional<float> velocityX = vt.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional<float> velocityY = vt.getVelocity(AMOTION_EVENT_AXIS_Y, 0); + ASSERT_TRUE(velocityX); + ASSERT_TRUE(velocityY); + + EXPECT_NEAR_BY_FRACTION(*velocityX, velocity, QUADRATIC_VELOCITY_TOLERANCE); + EXPECT_NEAR_BY_FRACTION(*velocityY, velocity, QUADRATIC_VELOCITY_TOLERANCE); } /* @@ -461,8 +456,6 @@ TEST_F(VelocityTrackerTest, TestApiInteractionsWithNoMotionEvents) { EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); - EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); - VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000); for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)); @@ -1074,7 +1067,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { * If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be * part of the fitted data), this can cause large velocity values to be reported instead. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_ThreeFingerTap) { std::vector<PlanarMotionEventEntry> motions = { { 0us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, { 10800us, {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN @@ -1162,7 +1155,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { * ================== Tests for least squares fitting ============================================== * * Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy - * getEstimator function. In particular: + * getVelocity function. In particular: * - inside the function, time gets converted from nanoseconds to seconds * before being used in the fit. * - any values that are older than 100 ms are being discarded. @@ -1183,7 +1176,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { * The coefficients are (0, 0, 1). * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2). */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Constant) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, // 0 s { 1ms, {{1, 1}} }, // 0.001 s @@ -1195,13 +1188,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan // -0.002, 1 // -0.001, 1 // -0.ms, 1 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({1, 0, 0})); + computeAndCheckQuadraticVelocity(motions, 0); } /* * Straight line y = x :: the constant and quadratic coefficients are zero. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Linear) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{-2, -2}} }, { 1ms, {{-1, -1}} }, @@ -1213,13 +1206,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) // -0.002, -2 // -0.001, -1 // -0.000, 0 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 1E3, 0})); + computeAndCheckQuadraticVelocity(motions, 1E3); } /* * Parabola */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, @@ -1231,13 +1224,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 1 // -0.001, 4 // -0.000, 8 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({8, 4.5E3, 0.5E6})); + computeAndCheckQuadraticVelocity(motions, 4.5E3); } /* * Parabola */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic2) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, @@ -1249,13 +1242,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 1 // -0.001, 4 // -0.000, 9 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({9, 6E3, 1E6})); + computeAndCheckQuadraticVelocity(motions, 6E3); } /* * Parabola :: y = x^2 :: the constant and linear coefficients are zero. */ -TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) { +TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic3) { std::vector<PlanarMotionEventEntry> motions = { { 0ms, {{4, 4}} }, { 1ms, {{1, 1}} }, @@ -1267,7 +1260,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol // -0.002, 4 // -0.001, 1 // -0.000, 0 - computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 0E3, 1E6})); + computeAndCheckQuadraticVelocity(motions, 0E3); } // Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity. diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index cd427f03cf..3d3a50a373 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4190,12 +4190,9 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); } } - - if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount, - args->pointerProperties)) { - LOG(ERROR) << "Invalid event: " << args->dump(); - return; - } + LOG_ALWAYS_FATAL_IF(!validateMotionEvent(args->action, args->actionButton, args->pointerCount, + args->pointerProperties), + "Invalid event: %s", args->dump().c_str()); uint32_t policyFlags = args->policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index f05223cce0..36f71bb481 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -219,7 +219,7 @@ void PowerAdvisor::sendActualWorkDuration() { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin, + halWrapper->sendActualWorkDuration(*actualDuration + sTargetSafetyMargin, TimePoint::now()); } } @@ -232,12 +232,11 @@ void PowerAdvisor::sendPredictedWorkDuration() { } const std::optional<Duration> predictedDuration = estimateWorkDuration(true); - if (predictedDuration.has_value()) { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin, + halWrapper->sendActualWorkDuration(*predictedDuration + sTargetSafetyMargin, TimePoint::now()); } } @@ -812,6 +811,10 @@ std::optional<Duration> AidlPowerHalWrapper::getTargetWorkDuration() { const bool AidlPowerHalWrapper::sTraceHintSessionData = base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false); +const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds( + base::GetIntProperty<int64_t>("debug.sf.hint_margin_us", + ticks<std::micro>(PowerAdvisor::kDefaultTargetSafetyMargin))); + PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() { if (!mHasHal) { return nullptr; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index d45e7cb572..c4cfdc3482 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -274,7 +274,8 @@ private: // An adjustable safety margin which pads the "actual" value sent to PowerHAL, // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error - static constexpr const Duration kTargetSafetyMargin{1ms}; + static const Duration sTargetSafetyMargin; + static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; // How long we expect hwc to run after the present call until it waits for the fence static constexpr const Duration kFenceWaitStartDelayValidated{150us}; diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index ded734efad..dcc29b9913 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -140,6 +140,10 @@ std::string jankTypeBitmaskToString(int32_t jankType) { janks.emplace_back("SurfaceFlinger Stuffing"); jankType &= ~JankType::SurfaceFlingerStuffing; } + if (jankType & JankType::Dropped) { + janks.emplace_back("Dropped Frame"); + jankType &= ~JankType::Dropped; + } // jankType should be 0 if all types of jank were checked for. LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType); @@ -264,6 +268,11 @@ int32_t jankTypeBitmaskToProto(int32_t jankType) { protoJank |= FrameTimelineEvent::JANK_SF_STUFFING; jankType &= ~JankType::SurfaceFlingerStuffing; } + if (jankType & JankType::Dropped) { + // Jank dropped does not append to other janks, it fully overrides. + protoJank |= FrameTimelineEvent::JANK_DROPPED; + jankType &= ~JankType::Dropped; + } // jankType should be 0 if all types of jank were checked for. LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType); @@ -365,8 +374,7 @@ void SurfaceFrame::setGpuComposition() { std::optional<int32_t> SurfaceFrame::getJankType() const { std::scoped_lock lock(mMutex); if (mPresentState == PresentState::Dropped) { - // Return no jank if it's a dropped frame since we cannot attribute a jank to a it. - return JankType::None; + return JankType::Dropped; } if (mActuals.presentTime == 0) { // Frame hasn't been presented yet. @@ -503,7 +511,8 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r // We classify prediction expired as AppDeadlineMissed as the // TokenManager::kMaxTokens we store is large enough to account for a // reasonable app, so prediction expire would mean a huge scheduling delay. - mJankType = JankType::AppDeadlineMissed; + mJankType = mPresentState != PresentState::Presented ? JankType::Dropped + : JankType::AppDeadlineMissed; deadlineDelta = -1; return; } @@ -594,17 +603,17 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r mJankType |= displayFrameJankType; } } + if (mPresentState != PresentState::Presented) { + mJankType = JankType::Dropped; + // Since frame was not presented, lets drop any present value + mActuals.presentTime = 0; + } } void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate, nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta) { std::scoped_lock lock(mMutex); - if (mPresentState != PresentState::Presented) { - // No need to update dropped buffers - return; - } - mActuals.presentTime = presentTime; nsecs_t deadlineDelta = 0; diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 763d058e28..0ef1e979b1 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,8 +19,8 @@ #include <memory> #include <string> -#include <ThreadContext.h> #include <android-base/thread_annotations.h> +#include <ThreadContext.h> #include <ftl/enum.h> #include <ftl/optional.h> #include <scheduler/Features.h> diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index d26ef3c821..8911430790 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -1198,7 +1198,7 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_predictionExpiredDoesNotTraceExpecte TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) { auto tracingSession = getTracingSessionForTest(); // Layer specific increment - EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)); + EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2); auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); @@ -1234,8 +1234,8 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) { auto protoDroppedSurfaceFrameActualStart = createProtoActualSurfaceFrameStart(traceCookie + 2, surfaceFrameToken, displayFrameToken1, sPidOne, sLayerNameOne, - FrameTimelineEvent::PRESENT_DROPPED, false, false, - FrameTimelineEvent::JANK_NONE, + FrameTimelineEvent::PRESENT_DROPPED, true, false, + FrameTimelineEvent::JANK_DROPPED, FrameTimelineEvent::PREDICTION_VALID, true); auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2); @@ -1470,7 +1470,7 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDroppedFramesTraced createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken, displayFrameToken, sPidOne, sLayerNameOne, FrameTimelineEvent::PRESENT_DROPPED, false, false, - FrameTimelineEvent::JANK_NONE, + FrameTimelineEvent::JANK_DROPPED, FrameTimelineEvent::PREDICTION_EXPIRED, true); auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1); diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 2d66d3cf92..d22ce17258 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -42,12 +42,12 @@ public: void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod); void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime); Duration getFenceWaitDelayDuration(bool skipValidate); + Duration getErrorMargin(); protected: TestableSurfaceFlinger mFlinger; std::unique_ptr<PowerAdvisor> mPowerAdvisor; NiceMock<MockAidlPowerHalWrapper>* mMockAidlWrapper; - Duration kErrorMargin = 1ms; }; void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) { @@ -77,6 +77,8 @@ void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncP mPowerAdvisor->setCommitStart(startTime); mPowerAdvisor->setFrameDelay(0ns); mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); + ON_CALL(*mMockAidlWrapper, getTargetWorkDuration()) + .WillByDefault(Return(std::make_optional(vsyncPeriod))); } Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { @@ -84,6 +86,10 @@ Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { : PowerAdvisor::kFenceWaitStartDelayValidated); } +Duration PowerAdvisorTest::getErrorMargin() { + return mPowerAdvisor->sTargetSafetyMargin; +} + namespace { TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { @@ -109,7 +115,7 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -145,7 +151,7 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + + const Duration expectedDuration = getErrorMargin() + presentDuration + getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); @@ -185,7 +191,7 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h index 5654691884..3ed85e0b1f 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h @@ -47,6 +47,8 @@ public: MOCK_METHOD(void, sendActualWorkDuration, (Duration actualDuration, TimePoint timestamp), (override)); MOCK_METHOD(bool, shouldReconnectHAL, (), (override)); + MOCK_METHOD(std::vector<int32_t>, getPowerHintSessionThreadIds, (), (override)); + MOCK_METHOD(std::optional<Duration>, getTargetWorkDuration, (), (override)); }; } // namespace android::Hwc2::mock
\ No newline at end of file |