diff options
| author | 2025-02-26 11:55:07 -0800 | |
|---|---|---|
| committer | 2025-02-26 11:55:07 -0800 | |
| commit | 98bdc04b7658fde0a99403fc052d1d18e7d48ea6 (patch) | |
| tree | eddfcd420408117ba0399a190f75c13cf2db0036 /libs | |
| parent | 7ba28a3a24fadce84a590a6f4a94907840fe814c (diff) | |
| parent | 8c6afcf151af438342729f2399c43560ae1f353c (diff) | |
Merge 25Q1 (ab/12770256) to aosp-main-future
Bug: 385190204
Merged-In: I0fb567cbcca67a2fc6c088f652c8af570b8d7e53
Change-Id: Iaae8cd491ff963cf422f4b19c54be33e1244a9a1
Diffstat (limited to 'libs')
140 files changed, 5247 insertions, 990 deletions
diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp index 125cfaffa4..334d84b6b5 100644 --- a/libs/battery/LongArrayMultiStateCounter.cpp +++ b/libs/battery/LongArrayMultiStateCounter.cpp @@ -21,58 +21,137 @@ namespace android { namespace battery { -template <> -bool LongArrayMultiStateCounter::delta(const std::vector<uint64_t>& previousValue, - const std::vector<uint64_t>& newValue, - std::vector<uint64_t>* outValue) const { - size_t size = previousValue.size(); - if (newValue.size() != size) { - ALOGE("Incorrect array size: %d, should be %d", (int)newValue.size(), (int)size); - return false; +Uint64ArrayRW::Uint64ArrayRW(const Uint64Array ©) : Uint64Array(copy.size()) { + if (mSize != 0 && copy.data() != nullptr) { + mData = new uint64_t[mSize]; + memcpy(mData, copy.data(), mSize * sizeof(uint64_t)); + } else { + mData = nullptr; } +} - bool is_delta_valid = true; - for (int i = size - 1; i >= 0; i--) { - if (newValue[i] >= previousValue[i]) { - (*outValue)[i] = newValue[i] - previousValue[i]; +uint64_t *Uint64ArrayRW::dataRW() { + if (mData == nullptr) { + mData = new uint64_t[mSize]; + memset(mData, 0, mSize * sizeof(uint64_t)); + } + return mData; +} + +Uint64ArrayRW &Uint64ArrayRW::operator=(const Uint64Array &t) { + if (t.size() != mSize) { + delete[] mData; + mSize = t.size(); + mData = nullptr; + } + if (mSize != 0) { + if (t.data() != nullptr) { + if (mData == nullptr) { + mData = new uint64_t[mSize]; + } + memcpy(mData, t.data(), mSize * sizeof(uint64_t)); } else { - (*outValue)[i] = 0; - is_delta_valid = false; + delete[] mData; + mData = nullptr; } } - return is_delta_valid; + return *this; +} + +std::ostream &operator<<(std::ostream &os, const Uint64Array &v) { + os << "{"; + const uint64_t *data = v.data(); + if (data != nullptr) { + bool first = true; + for (size_t i = 0; i < v.size(); i++) { + if (!first) { + os << ", "; + } + os << data[i]; + first = false; + } + } + os << "}"; + return os; +} + +// Convenience constructor for tests +Uint64ArrayRW::Uint64ArrayRW(std::initializer_list<uint64_t> init) : Uint64Array(init.size()) { + mData = new uint64_t[mSize]; + memcpy(mData, init.begin(), mSize * sizeof(uint64_t)); +} + +// Used in tests only. +bool Uint64Array::operator==(const Uint64Array &other) const { + if (size() != other.size()) { + return false; + } + const uint64_t* thisData = data(); + const uint64_t* thatData = other.data(); + for (size_t i = 0; i < mSize; i++) { + const uint64_t v1 = thisData != nullptr ? thisData[i] : 0; + const uint64_t v2 = thatData != nullptr ? thatData[i] : 0; + if (v1 != v2) { + return false; + } + } + return true; } template <> -void LongArrayMultiStateCounter::add(std::vector<uint64_t>* value1, - const std::vector<uint64_t>& value2, const uint64_t numerator, - const uint64_t denominator) const { +void LongArrayMultiStateCounter::add(Uint64ArrayRW *value1, const Uint64Array &value2, + const uint64_t numerator, const uint64_t denominator) const { + const uint64_t* data2 = value2.data(); + if (data2 == nullptr) { + return; + } + + uint64_t* data1 = value1->dataRW(); + size_t size = value2.size(); if (numerator != denominator) { - for (int i = value2.size() - 1; i >= 0; i--) { + for (size_t i = 0; i < size; i++) { // The caller ensures that denominator != 0 - (*value1)[i] += value2[i] * numerator / denominator; + data1[i] += data2[i] * numerator / denominator; } } else { - for (int i = value2.size() - 1; i >= 0; i--) { - (*value1)[i] += value2[i]; + for (size_t i = 0; i < size; i++) { + data1[i] += data2[i]; } } } -template <> -std::string LongArrayMultiStateCounter::valueToString(const std::vector<uint64_t>& v) const { - std::stringstream s; - s << "{"; - bool first = true; - for (uint64_t n : v) { - if (!first) { - s << ", "; +template<> +bool LongArrayMultiStateCounter::delta(const Uint64ArrayRW &previousValue, + const Uint64Array &newValue, Uint64ArrayRW *outValue) const { + size_t size = previousValue.size(); + if (newValue.size() != size) { + ALOGE("Incorrect array size: %d, should be %d", (int) newValue.size(), (int) size); + return false; + } + if (outValue->size() != size) { + ALOGE("Incorrect outValue size: %d, should be %d", (int) outValue->size(), (int) size); + return false; + } + + bool is_delta_valid = true; + const uint64_t *prevData = previousValue.data(); + const uint64_t *newData = newValue.data(); + uint64_t *outData = outValue->dataRW(); + for (size_t i = 0; i < size; i++) { + if (prevData == nullptr) { + if (newData == nullptr) { + outData[i] = 0; + } else { + outData[i] = newData[i]; + } + } else if (newData == nullptr || newData[i] < prevData[i]) { + outData[i] = 0; + is_delta_valid = false; + } else { + outData[i] = newData[i] - prevData[i]; } - s << n; - first = false; } - s << "}"; - return s.str(); + return is_delta_valid; } } // namespace battery diff --git a/libs/battery/LongArrayMultiStateCounter.h b/libs/battery/LongArrayMultiStateCounter.h index f3439f6a0c..e00c96898e 100644 --- a/libs/battery/LongArrayMultiStateCounter.h +++ b/libs/battery/LongArrayMultiStateCounter.h @@ -23,7 +23,66 @@ namespace android { namespace battery { -typedef MultiStateCounter<std::vector<uint64_t>> LongArrayMultiStateCounter; +/** + * Wrapper for an array of uint64's. + */ +class Uint64Array { + protected: + size_t mSize; + + public: + Uint64Array() : Uint64Array(0) {} + + Uint64Array(size_t size) : mSize(size) {} + + virtual ~Uint64Array() {} + + size_t size() const { return mSize; } + + /** + * Returns the wrapped array. + * + * Nullable! Null should be interpreted the same as an array of zeros + */ + virtual const uint64_t *data() const { return nullptr; } + + friend std::ostream &operator<<(std::ostream &os, const Uint64Array &v); + + // Test API + bool operator==(const Uint64Array &other) const; +}; + +/** + * Mutable version of Uint64Array. + */ +class Uint64ArrayRW: public Uint64Array { + uint64_t* mData; + +public: + Uint64ArrayRW() : Uint64ArrayRW(0) {} + + Uint64ArrayRW(size_t size) : Uint64Array(size), mData(nullptr) {} + + Uint64ArrayRW(const Uint64Array ©); + + // Need an explicit copy constructor. In the initialization context C++ does not understand that + // a Uint64ArrayRW is a Uint64Array. + Uint64ArrayRW(const Uint64ArrayRW ©) : Uint64ArrayRW((const Uint64Array &) copy) {} + + // Test API + Uint64ArrayRW(std::initializer_list<uint64_t> init); + + ~Uint64ArrayRW() override { delete[] mData; } + + const uint64_t *data() const override { return mData; } + + // NonNull. Will initialize the wrapped array if it is null. + uint64_t *dataRW(); + + Uint64ArrayRW &operator=(const Uint64Array &t); +}; + +typedef MultiStateCounter<Uint64ArrayRW, Uint64Array> LongArrayMultiStateCounter; } // namespace battery } // namespace android diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp index e4e6b2a49f..1c74e3fd14 100644 --- a/libs/battery/LongArrayMultiStateCounterTest.cpp +++ b/libs/battery/LongArrayMultiStateCounterTest.cpp @@ -24,25 +24,25 @@ namespace battery { class LongArrayMultiStateCounterTest : public testing::Test {}; TEST_F(LongArrayMultiStateCounterTest, stateChange) { - LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4)); - testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000); + LongArrayMultiStateCounter testCounter(2, Uint64Array(4)); + testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000); testCounter.setState(0, 1000); testCounter.setState(1, 2000); - testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000); + testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000); // Time was split in half between the two states, so the counts will be split 50:50 too - EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(0)); - EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(1)); + EXPECT_EQ(Uint64ArrayRW({50, 100, 150, 200}), testCounter.getCount(0)); + EXPECT_EQ(Uint64ArrayRW({50, 100, 150, 200}), testCounter.getCount(1)); } TEST_F(LongArrayMultiStateCounterTest, accumulation) { - LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4)); - testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000); + LongArrayMultiStateCounter testCounter(2, Uint64Array(4)); + testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000); testCounter.setState(0, 1000); testCounter.setState(1, 2000); - testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000); + testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000); testCounter.setState(0, 4000); - testCounter.updateValue(std::vector<uint64_t>({200, 300, 400, 500}), 8000); + testCounter.updateValue(Uint64ArrayRW({200, 300, 400, 500}), 8000); // The first delta is split 50:50: // 0: {50, 100, 150, 200} @@ -50,16 +50,16 @@ TEST_F(LongArrayMultiStateCounterTest, accumulation) { // The second delta is split 4:1 // 0: {80, 80, 80, 80} // 1: {20, 20, 20, 20} - EXPECT_EQ(std::vector<uint64_t>({130, 180, 230, 280}), testCounter.getCount(0)); - EXPECT_EQ(std::vector<uint64_t>({70, 120, 170, 220}), testCounter.getCount(1)); + EXPECT_EQ(Uint64ArrayRW({130, 180, 230, 280}), testCounter.getCount(0)); + EXPECT_EQ(Uint64ArrayRW({70, 120, 170, 220}), testCounter.getCount(1)); } TEST_F(LongArrayMultiStateCounterTest, toString) { - LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4)); - testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000); + LongArrayMultiStateCounter testCounter(2, Uint64Array(4)); + testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000); testCounter.setState(0, 1000); testCounter.setState(1, 2000); - testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000); + testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000); EXPECT_STREQ("[0: {50, 100, 150, 200}, 1: {50, 100, 150, 200}] updated: 3000 currentState: 1", testCounter.toString().c_str()); diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h index 04b718698e..fadc4ffd41 100644 --- a/libs/battery/MultiStateCounter.h +++ b/libs/battery/MultiStateCounter.h @@ -35,12 +35,12 @@ namespace battery { typedef uint16_t state_t; -template <class T> +template <class T, class V> class MultiStateCounter { - uint16_t stateCount; + const uint16_t stateCount; + const V emptyValue; state_t currentState; time_t lastStateChangeTimestamp; - T emptyValue; T lastValue; time_t lastUpdateTimestamp; T deltaValue; @@ -54,7 +54,7 @@ class MultiStateCounter { State* states; public: - MultiStateCounter(uint16_t stateCount, const T& emptyValue); + MultiStateCounter(uint16_t stateCount, const V& emptyValue); virtual ~MultiStateCounter(); @@ -66,35 +66,35 @@ public: * Copies the current state and accumulated times-in-state from the source. Resets * the accumulated value. */ - void copyStatesFrom(const MultiStateCounter<T>& source); + void copyStatesFrom(const MultiStateCounter<T, V> &source); - void setValue(state_t state, const T& value); + void setValue(state_t state, const V& value); /** * Updates the value by distributing the delta from the previously set value * among states according to their respective time-in-state. * Returns the delta from the previously set value. */ - const T& updateValue(const T& value, time_t timestamp); + const V& updateValue(const V& value, time_t timestamp); /** * Updates the value by distributing the specified increment among states according * to their respective time-in-state. */ - void incrementValue(const T& increment, time_t timestamp); + void incrementValue(const V& increment, time_t timestamp); /** * Adds the specified increment to the value for the current state, without affecting * the last updated value or timestamp. Ignores partial time-in-state: the entirety of * the increment is given to the current state. */ - void addValue(const T& increment); + void addValue(const V& increment); void reset(); uint16_t getStateCount(); - const T& getCount(state_t state); + const V& getCount(state_t state); std::string toString(); @@ -104,27 +104,25 @@ private: * Returns true iff the combination of previousValue and newValue is valid * (newValue >= prevValue) */ - bool delta(const T& previousValue, const T& newValue, T* outValue) const; + bool delta(const T& previousValue, const V& newValue, T* outValue) const; /** * Adds value2 to value1 and stores the result in value1. Denominator is * guaranteed to be non-zero. */ - void add(T* value1, const T& value2, const uint64_t numerator, + void add(T* value1, const V& value2, const uint64_t numerator, const uint64_t denominator) const; - - std::string valueToString(const T& value) const; }; // ---------------------- MultiStateCounter Implementation ------------------------- // Since MultiStateCounter is a template, the implementation must be inlined. -template <class T> -MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue) +template <class T, class V> +MultiStateCounter<T, V>::MultiStateCounter(uint16_t stateCount, const V& emptyValue) : stateCount(stateCount), + emptyValue(emptyValue), currentState(0), lastStateChangeTimestamp(-1), - emptyValue(emptyValue), lastValue(emptyValue), lastUpdateTimestamp(-1), deltaValue(emptyValue), @@ -136,13 +134,13 @@ MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue } } -template <class T> -MultiStateCounter<T>::~MultiStateCounter() { +template <class T, class V> +MultiStateCounter<T, V>::~MultiStateCounter() { delete[] states; }; -template <class T> -void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) { +template <class T, class V> +void MultiStateCounter<T, V>::setEnabled(bool enabled, time_t timestamp) { if (enabled == isEnabled) { return; } @@ -167,8 +165,8 @@ void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) { } } -template <class T> -void MultiStateCounter<T>::setState(state_t state, time_t timestamp) { +template <class T, class V> +void MultiStateCounter<T, V>::setState(state_t state, time_t timestamp) { if (isEnabled && lastStateChangeTimestamp >= 0 && lastUpdateTimestamp >= 0) { // If the update arrived out-of-order, just push back the timestamp to // avoid having the situation where timeInStateSinceUpdate > timeSinceUpdate @@ -198,8 +196,8 @@ void MultiStateCounter<T>::setState(state_t state, time_t timestamp) { lastStateChangeTimestamp = timestamp; } -template <class T> -void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) { +template <class T, class V> +void MultiStateCounter<T, V>::copyStatesFrom(const MultiStateCounter<T, V>& source) { if (stateCount != source.stateCount) { ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount); return; @@ -214,14 +212,14 @@ void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) { lastUpdateTimestamp = source.lastUpdateTimestamp; } -template <class T> -void MultiStateCounter<T>::setValue(state_t state, const T& value) { +template <class T, class V> +void MultiStateCounter<T, V>::setValue(state_t state, const V& value) { states[state].counter = value; } -template <class T> -const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) { - T* returnValue = &emptyValue; +template <class T, class V> +const V& MultiStateCounter<T, V>::updateValue(const V& value, time_t timestamp) { + const V* returnValue = &emptyValue; // If the counter is disabled, we ignore the update, except when the counter got disabled after // the previous update, in which case we still need to pick up the residual delta. @@ -250,8 +248,8 @@ const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) { } } else { std::stringstream str; - str << "updateValue is called with a value " << valueToString(value) - << ", which is lower than the previous value " << valueToString(lastValue) + str << "updateValue is called with a value " << value + << ", which is lower than the previous value " << lastValue << "\n"; ALOGE("%s", str.str().c_str()); @@ -276,23 +274,25 @@ const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) { return *returnValue; } -template <class T> -void MultiStateCounter<T>::incrementValue(const T& increment, time_t timestamp) { +template <class T, class V> +void MultiStateCounter<T, V>::incrementValue(const V& increment, time_t timestamp) { +// T newValue; +// newValue = lastValue; // Copy assignment, not initialization. T newValue = lastValue; add(&newValue, increment, 1 /* numerator */, 1 /* denominator */); updateValue(newValue, timestamp); } -template <class T> -void MultiStateCounter<T>::addValue(const T& value) { +template <class T, class V> +void MultiStateCounter<T, V>::addValue(const V& value) { if (!isEnabled) { return; } add(&states[currentState].counter, value, 1 /* numerator */, 1 /* denominator */); } -template <class T> -void MultiStateCounter<T>::reset() { +template <class T, class V> +void MultiStateCounter<T, V>::reset() { lastStateChangeTimestamp = -1; lastUpdateTimestamp = -1; for (int i = 0; i < stateCount; i++) { @@ -301,25 +301,26 @@ void MultiStateCounter<T>::reset() { } } -template <class T> -uint16_t MultiStateCounter<T>::getStateCount() { +template <class T, class V> +uint16_t MultiStateCounter<T, V>::getStateCount() { return stateCount; } -template <class T> -const T& MultiStateCounter<T>::getCount(state_t state) { +template <class T, class V> +const V& MultiStateCounter<T, V>::getCount(state_t state) { return states[state].counter; } -template <class T> -std::string MultiStateCounter<T>::toString() { +template <class T, class V> +std::string MultiStateCounter<T, V>::toString() { std::stringstream str; +// str << "LAST VALUE: " << valueToString(lastValue); str << "["; for (int i = 0; i < stateCount; i++) { if (i != 0) { str << ", "; } - str << i << ": " << valueToString(states[i].counter); + str << i << ": " << states[i].counter; if (states[i].timeInStateSinceUpdate > 0) { str << " timeInStateSinceUpdate: " << states[i].timeInStateSinceUpdate; } diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp index a51a38a6c7..589b7fe4e3 100644 --- a/libs/battery/MultiStateCounterTest.cpp +++ b/libs/battery/MultiStateCounterTest.cpp @@ -21,7 +21,7 @@ namespace android { namespace battery { -typedef MultiStateCounter<double> DoubleMultiStateCounter; +typedef MultiStateCounter<double, double> DoubleMultiStateCounter; template <> bool DoubleMultiStateCounter::delta(const double& previousValue, const double& newValue, @@ -41,11 +41,6 @@ void DoubleMultiStateCounter::add(double* value1, const double& value2, const ui } } -template <> -std::string DoubleMultiStateCounter::valueToString(const double& v) const { - return std::to_string(v); -} - class MultiStateCounterTest : public testing::Test {}; TEST_F(MultiStateCounterTest, constructor) { diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index 1e83c350b1..9f5d822c55 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -180,7 +180,7 @@ static void acquire_object(const sp<ProcessState>& proc, const flat_binder_objec } } - ALOGD("Invalid object type 0x%08x", obj.hdr.type); + ALOGE("Invalid object type 0x%08x to acquire", obj.hdr.type); } static void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj, @@ -210,7 +210,7 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec } } - ALOGE("Invalid object type 0x%08x", obj.hdr.type); + ALOGE("Invalid object type 0x%08x to release", obj.hdr.type); } #endif // BINDER_WITH_KERNEL_IPC @@ -1162,31 +1162,6 @@ status_t Parcel::finishWrite(size_t len) return NO_ERROR; } -status_t Parcel::writeUnpadded(const void* data, size_t len) -{ - if (len > INT32_MAX) { - // don't accept size_t values which may have come from an - // inadvertent conversion from a negative int. - return BAD_VALUE; - } - - size_t end = mDataPos + len; - if (end < mDataPos) { - // integer overflow - return BAD_VALUE; - } - - if (end <= mDataCapacity) { -restart_write: - memcpy(mData+mDataPos, data, len); - return finishWrite(len); - } - - status_t err = growData(len); - if (err == NO_ERROR) goto restart_write; - return err; -} - status_t Parcel::write(const void* data, size_t len) { if (len > INT32_MAX) { @@ -1223,6 +1198,10 @@ restart_write: //printf("Writing %ld bytes, padded to %ld\n", len, padded); uint8_t* const data = mData+mDataPos; + if (status_t status = validateReadData(mDataPos + padded); status != OK) { + return nullptr; // drops status + } + // Need to pad at end? if (padded != len) { #if BYTE_ORDER == BIG_ENDIAN @@ -1803,6 +1782,10 @@ status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData) const bool enoughObjects = kernelFields->mObjectsSize < kernelFields->mObjectsCapacity; if (enoughData && enoughObjects) { restart_write: + if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) { + return status; + } + *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val; // remember if it's a file descriptor @@ -1878,7 +1861,10 @@ status_t Parcel::validateReadData(size_t upperBound) const if (mDataPos < kernelFields->mObjects[nextObject] + sizeof(flat_binder_object)) { // Requested info overlaps with an object if (!mServiceFuzzing) { - ALOGE("Attempt to read from protected data in Parcel %p", this); + ALOGE("Attempt to read or write from protected data in Parcel %p. pos: " + "%zu, nextObject: %zu, object offset: %llu, object size: %zu", + this, mDataPos, nextObject, kernelFields->mObjects[nextObject], + sizeof(flat_binder_object)); } return PERMISSION_DENIED; } @@ -2046,6 +2032,10 @@ status_t Parcel::writeAligned(T val) { if ((mDataPos+sizeof(val)) <= mDataCapacity) { restart_write: + if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) { + return status; + } + memcpy(mData + mDataPos, &val, sizeof(val)); return finishWrite(sizeof(val)); } @@ -2236,9 +2226,7 @@ const char* Parcel::readCString() const const char* eos = reinterpret_cast<const char*>(memchr(str, 0, avail)); if (eos) { const size_t len = eos - str; - mDataPos += pad_size(len+1); - ALOGV("readCString Setting data pos of %p to %zu", this, mDataPos); - return str; + return static_cast<const char*>(readInplace(len + 1)); } } return nullptr; @@ -2682,14 +2670,14 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const } #endif // BINDER_WITH_KERNEL_IPC -void Parcel::closeFileDescriptors() { +void Parcel::closeFileDescriptors(size_t newObjectsSize) { if (auto* kernelFields = maybeKernelFields()) { #ifdef BINDER_WITH_KERNEL_IPC size_t i = kernelFields->mObjectsSize; if (i > 0) { // ALOGI("Closing file descriptors for %zu objects...", i); } - while (i > 0) { + while (i > newObjectsSize) { i--; const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]); @@ -2700,6 +2688,7 @@ void Parcel::closeFileDescriptors() { } } #else // BINDER_WITH_KERNEL_IPC + (void)newObjectsSize; LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time"); (void)kernelFields; #endif // BINDER_WITH_KERNEL_IPC @@ -2925,7 +2914,7 @@ void Parcel::freeDataNoInit() //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); auto* kernelFields = maybeKernelFields(); // Close FDs before freeing, otherwise they will leak for kernel binder. - closeFileDescriptors(); + closeFileDescriptors(/*newObjectsSize=*/0); mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr, kernelFields ? kernelFields->mObjectsSize : 0); } else { @@ -2953,6 +2942,14 @@ status_t Parcel::growData(size_t len) return BAD_VALUE; } + if (mDataPos > mDataSize) { + // b/370831157 - this case used to abort. We also don't expect mDataPos < mDataSize, but + // this would only waste a bit of memory, so it's okay. + ALOGE("growData only expected at the end of a Parcel. pos: %zu, size: %zu, capacity: %zu", + mDataPos, len, mDataCapacity); + return BAD_VALUE; + } + if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow size_t newSize = ((mDataSize+len)*3)/2; @@ -3054,13 +3051,38 @@ status_t Parcel::continueWrite(size_t desired) objectsSize = 0; } else { if (kernelFields) { +#ifdef BINDER_WITH_KERNEL_IPC + validateReadData(mDataSize); // hack to sort the objects while (objectsSize > 0) { - if (kernelFields->mObjects[objectsSize - 1] < desired) break; + if (kernelFields->mObjects[objectsSize - 1] + sizeof(flat_binder_object) <= + desired) + break; objectsSize--; } +#endif // BINDER_WITH_KERNEL_IPC } else { while (objectsSize > 0) { - if (rpcFields->mObjectPositions[objectsSize - 1] < desired) break; + // Object size varies by type. + uint32_t pos = rpcFields->mObjectPositions[objectsSize - 1]; + size_t size = sizeof(RpcFields::ObjectType); + uint32_t minObjectEnd; + if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) || + minObjectEnd > mDataSize) { + return BAD_VALUE; + } + const auto type = *reinterpret_cast<const RpcFields::ObjectType*>(mData + pos); + switch (type) { + case RpcFields::TYPE_BINDER_NULL: + break; + case RpcFields::TYPE_BINDER: + size += sizeof(uint64_t); // address + break; + case RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR: + size += sizeof(int32_t); // fd index + break; + } + + if (pos + size <= desired) break; objectsSize--; } } @@ -3109,15 +3131,24 @@ status_t Parcel::continueWrite(size_t desired) if (mData) { memcpy(data, mData, mDataSize < desired ? mDataSize : desired); } +#ifdef BINDER_WITH_KERNEL_IPC if (objects && kernelFields && kernelFields->mObjects) { memcpy(objects, kernelFields->mObjects, objectsSize * sizeof(binder_size_t)); + // All FDs are owned when `mOwner`, even when `cookie == 0`. When + // we switch to `!mOwner`, we need to explicitly mark the FDs as + // owned. + for (size_t i = 0; i < objectsSize; i++) { + flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data + objects[i]); + if (flat->hdr.type == BINDER_TYPE_FD) { + flat->cookie = 1; + } + } } // ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); if (kernelFields) { - // TODO(b/239222407): This seems wrong. We should only free FDs when - // they are in a truncated section of the parcel. - closeFileDescriptors(); + closeFileDescriptors(objectsSize); } +#endif // BINDER_WITH_KERNEL_IPC mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr, kernelFields ? kernelFields->mObjectsSize : 0); mOwner = nullptr; @@ -3244,11 +3275,19 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) { } while (rpcFields->mObjectPositions.size() > newObjectsSize) { uint32_t pos = rpcFields->mObjectPositions.back(); - rpcFields->mObjectPositions.pop_back(); + uint32_t minObjectEnd; + if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) || + minObjectEnd > mDataSize) { + return BAD_VALUE; + } const auto type = *reinterpret_cast<const RpcFields::ObjectType*>(mData + pos); if (type == RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { - const auto fdIndex = - *reinterpret_cast<const int32_t*>(mData + pos + sizeof(RpcFields::ObjectType)); + uint32_t objectEnd; + if (__builtin_add_overflow(minObjectEnd, sizeof(int32_t), &objectEnd) || + objectEnd > mDataSize) { + return BAD_VALUE; + } + const auto fdIndex = *reinterpret_cast<const int32_t*>(mData + minObjectEnd); if (rpcFields->mFds == nullptr || fdIndex < 0 || static_cast<size_t>(fdIndex) >= rpcFields->mFds->size()) { ALOGE("RPC Parcel contains invalid file descriptor index. index=%d fd_count=%zu", @@ -3258,6 +3297,7 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) { // In practice, this always removes the last element. rpcFields->mFds->erase(rpcFields->mFds->begin() + fdIndex); } + rpcFields->mObjectPositions.pop_back(); } return OK; } diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h index 9cd2ae9c25..1154211582 100644 --- a/libs/binder/include/binder/Parcel.h +++ b/libs/binder/include/binder/Parcel.h @@ -172,14 +172,14 @@ public: LIBBINDER_EXPORTED status_t write(const void* data, size_t len); LIBBINDER_EXPORTED void* writeInplace(size_t len); - LIBBINDER_EXPORTED status_t writeUnpadded(const void* data, size_t len); LIBBINDER_EXPORTED status_t writeInt32(int32_t val); LIBBINDER_EXPORTED status_t writeUint32(uint32_t val); LIBBINDER_EXPORTED status_t writeInt64(int64_t val); LIBBINDER_EXPORTED status_t writeUint64(uint64_t val); LIBBINDER_EXPORTED status_t writeFloat(float val); LIBBINDER_EXPORTED status_t writeDouble(double val); - LIBBINDER_EXPORTED status_t writeCString(const char* str); + LIBBINDER_EXPORTED status_t writeCString(const char* str) + __attribute__((deprecated("use AIDL, writeString* instead"))); LIBBINDER_EXPORTED status_t writeString8(const String8& str); LIBBINDER_EXPORTED status_t writeString8(const char* str, size_t len); LIBBINDER_EXPORTED status_t writeString16(const String16& str); @@ -435,7 +435,8 @@ public: LIBBINDER_EXPORTED status_t readUtf8FromUtf16(std::unique_ptr<std::string>* str) const __attribute__((deprecated("use std::optional version instead"))); - LIBBINDER_EXPORTED const char* readCString() const; + LIBBINDER_EXPORTED const char* readCString() const + __attribute__((deprecated("use AIDL, use readString*"))); LIBBINDER_EXPORTED String8 readString8() const; LIBBINDER_EXPORTED status_t readString8(String8* pArg) const; LIBBINDER_EXPORTED const char* readString8Inplace(size_t* outLen) const; @@ -651,8 +652,8 @@ public: LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const; private: - // Explicitly close all file descriptors in the parcel. - void closeFileDescriptors(); + // Close all file descriptors in the parcel at object positions >= newObjectsSize. + void closeFileDescriptors(size_t newObjectsSize); // `objects` and `objectsSize` always 0 for RPC Parcels. typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects, diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 391a57010a..891c0a290c 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -48,6 +48,7 @@ #include <linux/sched.h> #include <sys/epoll.h> +#include <sys/mman.h> #include <sys/prctl.h> #include <sys/socket.h> #include <sys/un.h> @@ -112,6 +113,8 @@ enum BinderLibTestTranscationCode { BINDER_LIB_TEST_LINK_DEATH_TRANSACTION, BINDER_LIB_TEST_WRITE_FILE_TRANSACTION, BINDER_LIB_TEST_WRITE_PARCEL_FILE_DESCRIPTOR_TRANSACTION, + BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, + BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, BINDER_LIB_TEST_EXIT_TRANSACTION, BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION, BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION, @@ -548,6 +551,30 @@ class TestDeathRecipient : public IBinder::DeathRecipient, public BinderLibTestE }; }; +ssize_t countFds() { + return std::distance(std::filesystem::directory_iterator("/proc/self/fd"), + std::filesystem::directory_iterator{}); +} + +struct FdLeakDetector { + int startCount; + + FdLeakDetector() { + // This log statement is load bearing. We have to log something before + // counting FDs to make sure the logging system is initialized, otherwise + // the sockets it opens will look like a leak. + ALOGW("FdLeakDetector counting FDs."); + startCount = countFds(); + } + ~FdLeakDetector() { + int endCount = countFds(); + if (startCount != endCount) { + ADD_FAILURE() << "fd count changed (" << startCount << " -> " << endCount + << ") fd leak?"; + } + } +}; + TEST_F(BinderLibTest, CannotUseBinderAfterFork) { // EXPECT_DEATH works by forking the process EXPECT_DEATH({ ProcessState::self(); }, "libbinder ProcessState can not be used after fork"); @@ -1182,6 +1209,100 @@ TEST_F(BinderLibTest, PassParcelFileDescriptor) { EXPECT_EQ(0, read(read_end.get(), readbuf.data(), datasize)); } +TEST_F(BinderLibTest, RecvOwnedFileDescriptors) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndWriteInt) { + GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; + + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + reply.setDataPosition(reply.dataSize()); + reply.writeInt32(0); + reply.setDataPosition(0); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndTruncate) { + GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; + + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); +} + +TEST_F(BinderLibTest, RecvUnownedFileDescriptors) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndWriteInt) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + reply.setDataPosition(reply.dataSize()); + reply.writeInt32(0); + reply.setDataPosition(0); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndTruncate) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); +} + TEST_F(BinderLibTest, PromoteLocal) { sp<IBinder> strong = new BBinder(); wp<IBinder> weak = strong; @@ -2261,6 +2382,40 @@ public: if (ret != size) return UNKNOWN_ERROR; return NO_ERROR; } + case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION: { + unique_fd fd1(memfd_create("memfd1", MFD_CLOEXEC)); + if (!fd1.ok()) { + PLOGE("memfd_create failed"); + return UNKNOWN_ERROR; + } + unique_fd fd2(memfd_create("memfd2", MFD_CLOEXEC)); + if (!fd2.ok()) { + PLOGE("memfd_create failed"); + return UNKNOWN_ERROR; + } + status_t ret; + ret = reply->writeFileDescriptor(fd1.release(), true); + if (ret != NO_ERROR) { + return ret; + } + ret = reply->writeFileDescriptor(fd2.release(), true); + if (ret != NO_ERROR) { + return ret; + } + return NO_ERROR; + } + case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION: { + status_t ret; + ret = reply->writeFileDescriptor(STDOUT_FILENO, false); + if (ret != NO_ERROR) { + return ret; + } + ret = reply->writeFileDescriptor(STDERR_FILENO, false); + if (ret != NO_ERROR) { + return ret; + } + return NO_ERROR; + } case BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION: alarm(10); return NO_ERROR; diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp index 32a70e5b11..6259d9d2d2 100644 --- a/libs/binder/tests/binderParcelUnitTest.cpp +++ b/libs/binder/tests/binderParcelUnitTest.cpp @@ -33,6 +33,38 @@ using android::String8; using android::binder::Status; using android::binder::unique_fd; +static void checkCString(const char* str) { + for (size_t i = 0; i < 3; i++) { + Parcel p; + + for (size_t j = 0; j < i; j++) p.writeInt32(3); + + p.writeCString(str); + int32_t pos = p.dataPosition(); + + p.setDataPosition(0); + + for (size_t j = 0; j < i; j++) p.readInt32(); + const char* str2 = p.readCString(); + + ASSERT_EQ(std::string(str), str2); + ASSERT_EQ(pos, p.dataPosition()); + } +} + +TEST(Parcel, TestReadCString) { + // we should remove the *CString APIs, but testing them until + // they are deleted. + checkCString(""); + checkCString("a"); + checkCString("\n"); + checkCString("32"); + checkCString("321"); + checkCString("3210"); + checkCString("3210b"); + checkCString("123434"); +} + TEST(Parcel, NonNullTerminatedString8) { String8 kTestString = String8("test-is-good"); diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp index 20b6b44c62..a41726c313 100644 --- a/libs/binder/tests/parcel_fuzzer/binder.cpp +++ b/libs/binder/tests/parcel_fuzzer/binder.cpp @@ -25,6 +25,8 @@ #include <binder/ParcelableHolder.h> #include <binder/PersistableBundle.h> #include <binder/Status.h> +#include <fuzzbinder/random_binder.h> +#include <fuzzbinder/random_fd.h> #include <utils/Flattenable.h> #include "../../Utils.h" @@ -115,14 +117,6 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS { p.setDataPosition(pos); FUZZ_LOG() << "setDataPosition done"; }, - [] (const ::android::Parcel& p, FuzzedDataProvider& provider) { - size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024); - std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len); - FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null"); - // TODO: allow all read and write operations - (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size()); - FUZZ_LOG() << "setData done"; - }, PARCEL_READ_NO_STATUS(size_t, allowFds), PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors), PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders), @@ -402,5 +396,113 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS { FUZZ_LOG() << " toString() result: " << toString; }, }; + +std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS { + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call setDataSize"; + size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024); + p.setDataSize(len); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call setDataCapacity"; + size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024); + p.setDataCapacity(len); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call setData"; + size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024); + std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len); + p.setData(bytes.data(), bytes.size()); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) { + FUZZ_LOG() << "about to call appendFrom"; + + std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 4096)); + ::android::Parcel p2; + fillRandomParcel(&p2, FuzzedDataProvider(bytes.data(), bytes.size()), options); + + int32_t start = provider.ConsumeIntegral<int32_t>(); + int32_t len = provider.ConsumeIntegral<int32_t>(); + p.appendFrom(&p2, start, len); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call pushAllowFds"; + bool val = provider.ConsumeBool(); + p.pushAllowFds(val); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call restoreAllowFds"; + bool val = provider.ConsumeBool(); + p.restoreAllowFds(val); + }, + // markForBinder - covered by fillRandomParcel, aborts if called multiple times + // markForRpc - covered by fillRandomParcel, aborts if called multiple times + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call writeInterfaceToken"; + std::string interface = provider.ConsumeRandomLengthString(); + p.writeInterfaceToken(android::String16(interface.c_str())); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call setEnforceNoDataAvail"; + p.setEnforceNoDataAvail(provider.ConsumeBool()); + }, + [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call setServiceFuzzing"; + p.setServiceFuzzing(); + }, + [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call freeData"; + p.freeData(); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call write"; + size_t len = provider.ConsumeIntegralInRange<size_t>(0, 256); + std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len); + p.write(bytes.data(), bytes.size()); + }, + // write* - write functions all implemented by calling 'write' itself. + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) { + FUZZ_LOG() << "about to call writeStrongBinder"; + + // TODO: this logic is somewhat duplicated with random parcel + android::sp<android::IBinder> binder; + if (provider.ConsumeBool() && options->extraBinders.size() > 0) { + binder = options->extraBinders.at( + provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1)); + } else { + binder = android::getRandomBinder(&provider); + options->extraBinders.push_back(binder); + } + + p.writeStrongBinder(binder); + }, + [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call writeFileDescriptor (no ownership)"; + p.writeFileDescriptor(STDERR_FILENO, false /* takeOwnership */); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) { + FUZZ_LOG() << "about to call writeFileDescriptor (take ownership)"; + std::vector<unique_fd> fds = android::getRandomFds(&provider); + if (fds.size() == 0) return; + + p.writeDupFileDescriptor(fds.at(0).get()); + options->extraFds.insert(options->extraFds.end(), + std::make_move_iterator(fds.begin() + 1), + std::make_move_iterator(fds.end())); + }, + // TODO: writeBlob + // TODO: writeDupImmutableBlobFileDescriptor + // TODO: writeObject (or make the API private more likely) + [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call writeNoException"; + p.writeNoException(); + }, + [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call replaceCallingWorkSourceUid"; + uid_t uid = provider.ConsumeIntegral<uid_t>(); + p.replaceCallingWorkSourceUid(uid); + }, +}; + // clang-format on #pragma clang diagnostic pop diff --git a/libs/binder/tests/parcel_fuzzer/binder.h b/libs/binder/tests/parcel_fuzzer/binder.h index 0c51d68a37..b0ac140d3f 100644 --- a/libs/binder/tests/parcel_fuzzer/binder.h +++ b/libs/binder/tests/parcel_fuzzer/binder.h @@ -21,3 +21,4 @@ #include "parcel_fuzzer.h" extern std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS; +extern std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS; diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp index e3a337171f..3f8d71d1f9 100644 --- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp +++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp @@ -20,8 +20,11 @@ #include "aidl/parcelables/GenericDataParcelable.h" #include "aidl/parcelables/SingleDataParcelable.h" +#include <android/binder_libbinder.h> #include <android/binder_parcel_utils.h> #include <android/binder_parcelable_utils.h> +#include <fuzzbinder/random_binder.h> +#include <fuzzbinder/random_fd.h> #include "util.h" @@ -211,16 +214,51 @@ std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS{ binder_status_t status = AParcel_marshal(p.aParcel(), buffer, start, len); FUZZ_LOG() << "status: " << status; }, - [](const NdkParcelAdapter& /*p*/, FuzzedDataProvider& provider) { - FUZZ_LOG() << "about to unmarshal AParcel"; +}; +std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS{ + [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) { + FUZZ_LOG() << "about to call AParcel_writeStrongBinder"; + + // TODO: this logic is somewhat duplicated with random parcel + android::sp<android::IBinder> binder; + if (provider.ConsumeBool() && options->extraBinders.size() > 0) { + binder = options->extraBinders.at( + provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1)); + } else { + binder = android::getRandomBinder(&provider); + options->extraBinders.push_back(binder); + } + + ndk::SpAIBinder abinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(binder)); + AParcel_writeStrongBinder(p.aParcel(), abinder.get()); + }, + [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) { + FUZZ_LOG() << "about to call AParcel_writeParcelFileDescriptor"; + + auto fds = android::getRandomFds(&provider); + if (fds.size() == 0) return; + + AParcel_writeParcelFileDescriptor(p.aParcel(), fds.at(0).get()); + options->extraFds.insert(options->extraFds.end(), + std::make_move_iterator(fds.begin() + 1), + std::make_move_iterator(fds.end())); + }, + // all possible data writes can be done as a series of 4-byte reads + [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call AParcel_writeInt32"; + int32_t val = provider.ConsumeIntegral<int32_t>(); + AParcel_writeInt32(p.aParcel(), val); + }, + [] (NdkParcelAdapter& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call AParcel_reset"; + AParcel_reset(p.aParcel()); + }, + [](NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) { + FUZZ_LOG() << "about to call AParcel_unmarshal"; size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()); - std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len); - const uint8_t* buffer = parcelData.data(); - const size_t bufferLen = parcelData.size(); - NdkParcelAdapter adapter; - binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, bufferLen); + std::vector<uint8_t> data = provider.ConsumeBytes<uint8_t>(len); + binder_status_t status = AParcel_unmarshal(p.aParcel(), data.data(), data.size()); FUZZ_LOG() << "status: " << status; }, - }; // clang-format on diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.h b/libs/binder/tests/parcel_fuzzer/binder_ndk.h index d19f25bc88..0c8b725400 100644 --- a/libs/binder/tests/parcel_fuzzer/binder_ndk.h +++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.h @@ -50,3 +50,4 @@ private: }; extern std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS; +extern std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS; diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp index a57d07fb24..192f9d5dce 100644 --- a/libs/binder/tests/parcel_fuzzer/main.cpp +++ b/libs/binder/tests/parcel_fuzzer/main.cpp @@ -80,6 +80,7 @@ void doTransactFuzz(const char* backend, const sp<B>& binder, FuzzedDataProvider (void)binder->transact(code, data, &reply, flag); } +// start with a Parcel full of data (e.g. like you get from another process) template <typename P> void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads, FuzzedDataProvider&& provider) { @@ -95,10 +96,10 @@ void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads, RandomParcelOptions options; P p; - fillRandomParcel(&p, std::move(provider), &options); + fillRandomParcel(&p, std::move(provider), &options); // consumes provider // since we are only using a byte to index - CHECK(reads.size() <= 255) << reads.size(); + CHECK_LE(reads.size(), 255u) << reads.size(); FUZZ_LOG() << "backend: " << backend; FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize()); @@ -115,26 +116,29 @@ void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads, } } -// Append two random parcels. template <typename P> -void doAppendFuzz(const char* backend, FuzzedDataProvider&& provider) { - int32_t start = provider.ConsumeIntegral<int32_t>(); - int32_t len = provider.ConsumeIntegral<int32_t>(); - - std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>( - provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes())); - - // same options so that FDs and binders could be shared in both Parcels +void doReadWriteFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads, + const std::vector<ParcelWrite<P>>& writes, FuzzedDataProvider&& provider) { RandomParcelOptions options; + P p; - P p0, p1; - fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options); - fillRandomParcel(&p1, std::move(provider), &options); + // since we are only using a byte to index + CHECK_LE(reads.size() + writes.size(), 255u) << reads.size(); FUZZ_LOG() << "backend: " << backend; - FUZZ_LOG() << "start: " << start << " len: " << len; - p0.appendFrom(&p1, start, len); + while (provider.remaining_bytes() > 0) { + uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1); + + FUZZ_LOG() << "Instruction " << idx << " avail: " << p.dataAvail() + << " pos: " << p.dataPosition() << " cap: " << p.dataCapacity(); + + if (idx < reads.size()) { + reads.at(idx)(p, provider); + } else { + writes.at(idx - reads.size())(p, provider, &options); + } + } } void* NothingClass_onCreate(void* args) { @@ -156,7 +160,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FuzzedDataProvider provider = FuzzedDataProvider(data, size); - const std::function<void(FuzzedDataProvider &&)> fuzzBackend[] = { + const std::function<void(FuzzedDataProvider&&)> fuzzBackend[] = { [](FuzzedDataProvider&& provider) { doTransactFuzz< ::android::hardware::Parcel>("hwbinder", @@ -187,10 +191,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::move(provider)); }, [](FuzzedDataProvider&& provider) { - doAppendFuzz<::android::Parcel>("binder", std::move(provider)); + doReadWriteFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS, + BINDER_PARCEL_WRITE_FUNCTIONS, + std::move(provider)); }, [](FuzzedDataProvider&& provider) { - doAppendFuzz<NdkParcelAdapter>("binder_ndk", std::move(provider)); + doReadWriteFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS, + BINDER_NDK_PARCEL_WRITE_FUNCTIONS, + std::move(provider)); }, }; diff --git a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h index 765a93e8c9..dbd0caea01 100644 --- a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h +++ b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h @@ -15,9 +15,13 @@ */ #pragma once +#include <fuzzbinder/random_parcel.h> #include <fuzzer/FuzzedDataProvider.h> #include <functional> template <typename P> using ParcelRead = std::function<void(const P& p, FuzzedDataProvider& provider)>; +template <typename P> +using ParcelWrite = std::function<void(P& p, FuzzedDataProvider& provider, + android::RandomParcelOptions* options)>; diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp index 1279d1147d..bb43e8d2a3 100644 --- a/libs/ftl/flags_test.cpp +++ b/libs/ftl/flags_test.cpp @@ -17,7 +17,7 @@ #include <ftl/flags.h> #include <gtest/gtest.h> -#include <type_traits> +#include <initializer_list> namespace android::test { @@ -59,6 +59,18 @@ TEST(Flags, All) { ASSERT_FALSE(flags.all(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE)); } +TEST(Flags, ImplicitConstructionAndAssignmentFromInitializerList) { + Flags<TestFlags> flags = {TestFlags::ONE, TestFlags::THREE}; + ASSERT_TRUE(flags.test(TestFlags::ONE)); + ASSERT_FALSE(flags.test(TestFlags::TWO)); + ASSERT_TRUE(flags.test(TestFlags::THREE)); + + flags = {}; + ASSERT_FALSE(flags.test(TestFlags::ONE)); + ASSERT_FALSE(flags.test(TestFlags::TWO)); + ASSERT_FALSE(flags.test(TestFlags::THREE)); +} + TEST(Flags, DefaultConstructor_hasNoFlagsSet) { Flags<TestFlags> flags; ASSERT_FALSE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE)); diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp index 367b398915..457237c35e 100644 --- a/libs/ftl/non_null_test.cpp +++ b/libs/ftl/non_null_test.cpp @@ -81,6 +81,31 @@ static_assert(static_cast<bool>(kApplePtr)); static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())), ftl::NonNull<const int*>>); +class Base {}; +class Derived : public Base {}; + +static_assert(std::is_constructible_v<ftl::NonNull<void*>, ftl::NonNull<int*>>); +static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<void*>>); +static_assert(std::is_constructible_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>); +static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>); +static_assert(std::is_constructible_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>); +static_assert(!std::is_constructible_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>); +static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<const int>>, + ftl::NonNull<std::unique_ptr<int>>>); +static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<Base>>, + ftl::NonNull<std::unique_ptr<Derived>>>); + +static_assert(std::is_assignable_v<ftl::NonNull<void*>, ftl::NonNull<int*>>); +static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<void*>>); +static_assert(std::is_assignable_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>); +static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>); +static_assert(std::is_assignable_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>); +static_assert(!std::is_assignable_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>); +static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<const int>>, + ftl::NonNull<std::unique_ptr<int>>>); +static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<Base>>, + ftl::NonNull<std::unique_ptr<Derived>>>); + } // namespace TEST(NonNull, SwapRawPtr) { @@ -156,4 +181,14 @@ TEST(NonNull, UnorderedSetOfSmartPtr) { EXPECT_TRUE(ftl::contains(spi, *spi.begin())); } +TEST(NonNull, ImplicitConversion) { + int i = 123; + int j = 345; + auto ip = ftl::as_non_null(&i); + ftl::NonNull<void*> vp{ip}; + EXPECT_EQ(vp.get(), &i); + vp = ftl::as_non_null(&j); + EXPECT_EQ(vp.get(), &j); +} + } // namespace android::test diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 1243b214d3..052b519db6 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -264,6 +264,7 @@ filegroup { "DisplayEventDispatcher.cpp", "DisplayEventReceiver.cpp", "FenceMonitor.cpp", + "Flags.cpp", "GLConsumer.cpp", "IConsumerListener.cpp", "IGraphicBufferConsumer.cpp", @@ -274,6 +275,7 @@ filegroup { "LayerMetadata.cpp", "LayerStatePermissions.cpp", "LayerState.cpp", + "DisplayLuts.cpp", "OccupancyTracker.cpp", "StreamSplitter.cpp", "ScreenCaptureResults.cpp", @@ -341,6 +343,10 @@ cc_library_shared { "libgui_aidl_headers", ], + static_libs: [ + "libsurfaceflingerflags", + ], + afdo: true, lto: { diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index ba82046388..5e6de78553 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -50,9 +50,27 @@ using namespace com::android::graphics::libgui; using namespace std::chrono_literals; namespace { + +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) +template <class Mutex> +class UnlockGuard { +public: + explicit UnlockGuard(Mutex& lock) : mLock{lock} { mLock.unlock(); } + + ~UnlockGuard() { mLock.lock(); } + + UnlockGuard(const UnlockGuard&) = delete; + UnlockGuard& operator=(const UnlockGuard&) = delete; + +private: + Mutex& mLock; +}; +#endif + inline const char* boolToString(bool b) { return b ? "true" : "false"; } + } // namespace namespace android { @@ -77,12 +95,6 @@ namespace android { std::unique_lock _lock{mutex}; \ base::ScopedLockAssertion assumeLocked(mutex); -#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) -static ReleaseBufferCallback EMPTY_RELEASE_CALLBACK = - [](const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/, - std::optional<uint32_t> /*currentMaxAcquiredBufferCount*/) {}; -#endif - void BLASTBufferItemConsumer::onDisconnect() { Mutex::Autolock lock(mMutex); mPreviouslyConnected = mCurrentlyConnected; @@ -223,9 +235,8 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati this); #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) - std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> bufferReleaseConsumer; - gui::BufferReleaseChannel::open(mName, bufferReleaseConsumer, mBufferReleaseProducer); - mBufferReleaseReader = std::make_shared<BufferReleaseReader>(std::move(bufferReleaseConsumer)); + gui::BufferReleaseChannel::open(mName, mBufferReleaseConsumer, mBufferReleaseProducer); + mBufferReleaseReader.emplace(*this); #endif BQA_LOGV("BLASTBufferQueue created"); @@ -257,9 +268,6 @@ BLASTBufferQueue::~BLASTBufferQueue() { void BLASTBufferQueue::onFirstRef() { // safe default, most producers are expected to override this mProducer->setMaxDequeuedBufferCount(2); -#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) - mBufferReleaseThread.start(sp<BLASTBufferQueue>::fromExisting(this)); -#endif } void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, @@ -276,18 +284,23 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, if (surfaceControlChanged && mSurfaceControl != nullptr) { BQA_LOGD("Updating SurfaceControl without recreating BBQ"); } - bool applyTransaction = false; // Always update the native object even though they might have the same layer handle, so we can // get the updated transform hint from WM. mSurfaceControl = surface; SurfaceComposerClient::Transaction t; + bool applyTransaction = false; if (surfaceControlChanged) { - t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure, - layer_state_t::eEnableBackpressure); #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) - t.setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer); + updateBufferReleaseProducer(); #endif + t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure, + layer_state_t::eEnableBackpressure); + // Migrate the picture profile handle to the new surface control. + if (com_android_graphics_libgui_flags_apply_picture_profiles() && + mPictureProfileHandle.has_value()) { + t.setPictureProfileHandle(mSurfaceControl, *mPictureProfileHandle); + } applyTransaction = true; } mTransformHint = mSurfaceControl->getTransformHint(); @@ -311,7 +324,7 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, } if (applyTransaction) { // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction - t.setApplyToken(mApplyToken).apply(false, true); + t.setApplyToken(mApplyToken).apply(false /* synchronous */, true /* oneWay */); } } @@ -405,7 +418,6 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence stat.latchTime, stat.frameEventStats.dequeueReadyTime); } -#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) auto currFrameNumber = stat.frameEventStats.frameNumber; std::vector<ReleaseCallbackId> staleReleases; for (const auto& [key, value]: mSubmitted) { @@ -421,7 +433,6 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence stat.currentMaxAcquiredBufferCount, true /* fakeRelease */); } -#endif } else { BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback"); } @@ -455,6 +466,9 @@ ReleaseBufferCallback BLASTBufferQueue::makeReleaseBufferCallbackThunk() { return; } bbq->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + bbq->drainBufferReleaseConsumer(); +#endif }; } @@ -521,8 +535,6 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence) { auto it = mSubmitted.find(callbackId); if (it == mSubmitted.end()) { - BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s", - callbackId.to_string().c_str()); return; } mNumAcquired--; @@ -632,12 +644,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform, bufferItem.mScalingMode, crop); -#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) - ReleaseBufferCallback releaseBufferCallback = - applyTransaction ? EMPTY_RELEASE_CALLBACK : makeReleaseBufferCallbackThunk(); -#else auto releaseBufferCallback = makeReleaseBufferCallbackThunk(); -#endif sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE; nsecs_t dequeueTime = -1; @@ -675,6 +682,17 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( if (!bufferItem.mIsAutoTimestamp) { t->setDesiredPresentTime(bufferItem.mTimestamp); } + if (com_android_graphics_libgui_flags_apply_picture_profiles() && + bufferItem.mPictureProfileHandle.has_value()) { + t->setPictureProfileHandle(mSurfaceControl, *bufferItem.mPictureProfileHandle); + // The current picture profile must be maintained in case the BBQ gets its + // SurfaceControl switched out. + mPictureProfileHandle = bufferItem.mPictureProfileHandle; + // Clear out the picture profile if the requestor has asked for it to be cleared + if (mPictureProfileHandle == PictureProfileHandle::NONE) { + mPictureProfileHandle = std::nullopt; + } + } // Drop stale frame timeline infos while (!mPendingFrameTimelines.empty() && @@ -1135,6 +1153,24 @@ public: #endif }; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) +class BBQBufferQueueCore : public BufferQueueCore { +public: + explicit BBQBufferQueueCore(const wp<BLASTBufferQueue>& bbq) : mBLASTBufferQueue{bbq} {} + + void notifyBufferReleased() const override { + sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); + if (!bbq) { + return; + } + bbq->mBufferReleaseReader->interruptBlockingRead(); + } + +private: + wp<BLASTBufferQueue> mBLASTBufferQueue; +}; +#endif + // Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls // can be non-blocking when the producer is in the client process. class BBQBufferQueueProducer : public BufferQueueProducer { @@ -1186,6 +1222,39 @@ public: return BufferQueueProducer::query(what, value); } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + status_t waitForBufferRelease(std::unique_lock<std::mutex>& bufferQueueLock, + nsecs_t timeout) const override { + sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); + if (!bbq) { + return OK; + } + + // BufferQueue has already checked if we have a free buffer. If there's an unread interrupt, + // we want to ignore it. This must be done before unlocking the BufferQueue lock to ensure + // we don't miss an interrupt. + bbq->mBufferReleaseReader->clearInterrupts(); + UnlockGuard unlockGuard{bufferQueueLock}; + + ATRACE_FORMAT("waiting for free buffer"); + ReleaseCallbackId id; + sp<Fence> fence; + uint32_t maxAcquiredBufferCount; + status_t status = + bbq->mBufferReleaseReader->readBlocking(id, fence, maxAcquiredBufferCount, timeout); + if (status == TIMED_OUT) { + return TIMED_OUT; + } else if (status != OK) { + // Waiting was interrupted or an error occurred. BufferQueueProducer will check if we + // have a free buffer and call this method again if not. + return OK; + } + + bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount); + return OK; + } +#endif + private: const wp<BLASTBufferQueue> mBLASTBufferQueue; }; @@ -1199,14 +1268,18 @@ void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer LOG_ALWAYS_FATAL_IF(outProducer == nullptr, "BLASTBufferQueue: outProducer must not be NULL"); LOG_ALWAYS_FATAL_IF(outConsumer == nullptr, "BLASTBufferQueue: outConsumer must not be NULL"); - sp<BufferQueueCore> core(new BufferQueueCore()); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + auto core = sp<BBQBufferQueueCore>::make(this); +#else + auto core = sp<BufferQueueCore>::make(); +#endif LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore"); - sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core, this)); + auto producer = sp<BBQBufferQueueProducer>::make(core, this); LOG_ALWAYS_FATAL_IF(producer == nullptr, "BLASTBufferQueue: failed to create BBQBufferQueueProducer"); - sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core)); + auto consumer = sp<BufferQueueConsumer>::make(core); consumer->setAllowExtraAcquire(true); LOG_ALWAYS_FATAL_IF(consumer == nullptr, "BLASTBufferQueue: failed to create BufferQueueConsumer"); @@ -1271,10 +1344,37 @@ void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) { #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) -BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader( - std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> endpoint) - : mEndpoint{std::move(endpoint)} { - mEpollFd = android::base::unique_fd{epoll_create1(0)}; +void BLASTBufferQueue::updateBufferReleaseProducer() { + // SELinux policy may prevent this process from sending the BufferReleaseChannel's file + // descriptor to SurfaceFlinger, causing the entire transaction to be dropped. We send this + // transaction independently of any other updates to ensure those updates aren't lost. + SurfaceComposerClient::Transaction t; + status_t status = t.setApplyToken(mApplyToken) + .setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer) + .apply(false /* synchronous */, true /* oneWay */); + if (status != OK) { + ALOGW("[%s] %s - failed to set buffer release channel on %s", mName.c_str(), + statusToString(status).c_str(), mSurfaceControl->getName().c_str()); + } +} + +void BLASTBufferQueue::drainBufferReleaseConsumer() { + ATRACE_CALL(); + while (true) { + ReleaseCallbackId id; + sp<Fence> fence; + uint32_t maxAcquiredBufferCount; + status_t status = + mBufferReleaseConsumer->readReleaseFence(id, fence, maxAcquiredBufferCount); + if (status != OK) { + return; + } + releaseBufferCallback(id, fence, maxAcquiredBufferCount); + } +} + +BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(BLASTBufferQueue& bbq) : mBbq{bbq} { + mEpollFd = android::base::unique_fd{epoll_create1(EPOLL_CLOEXEC)}; LOG_ALWAYS_FATAL_IF(!mEpollFd.ok(), "Failed to create buffer release epoll file descriptor. errno=%d " "message='%s'", @@ -1282,9 +1382,9 @@ BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader( epoll_event registerEndpointFd{}; registerEndpointFd.events = EPOLLIN; - registerEndpointFd.data.fd = mEndpoint->getFd(); - status_t status = - epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEndpoint->getFd(), ®isterEndpointFd); + registerEndpointFd.data.fd = mBbq.mBufferReleaseConsumer->getFd(); + status_t status = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mBbq.mBufferReleaseConsumer->getFd(), + ®isterEndpointFd); LOG_ALWAYS_FATAL_IF(status == -1, "Failed to register buffer release consumer file descriptor with epoll. " "errno=%d message='%s'", @@ -1306,78 +1406,64 @@ BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader( errno, strerror(errno)); } -BLASTBufferQueue::BufferReleaseReader& BLASTBufferQueue::BufferReleaseReader::operator=( - BufferReleaseReader&& other) { - if (this != &other) { - ftl::FakeGuard guard{mMutex}; - ftl::FakeGuard otherGuard{other.mMutex}; - mEndpoint = std::move(other.mEndpoint); - mEpollFd = std::move(other.mEpollFd); - mEventFd = std::move(other.mEventFd); - } - return *this; -} - status_t BLASTBufferQueue::BufferReleaseReader::readBlocking(ReleaseCallbackId& outId, sp<Fence>& outFence, - uint32_t& outMaxAcquiredBufferCount) { - epoll_event event{}; - while (true) { - int eventCount = epoll_wait(mEpollFd.get(), &event, 1 /* maxevents */, -1 /* timeout */); - if (eventCount == 1) { - break; - } - if (eventCount == -1 && errno != EINTR) { - ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno, - strerror(errno)); + uint32_t& outMaxAcquiredBufferCount, + nsecs_t timeout) { + // TODO(b/363290953) epoll_wait only has millisecond timeout precision. If timeout is less than + // 1ms, then we round timeout up to 1ms. Otherwise, we round timeout to the nearest + // millisecond. Once epoll_pwait2 can be used in libgui, we can specify timeout with nanosecond + // precision. + int timeoutMs = -1; + if (timeout == 0) { + timeoutMs = 0; + } else if (timeout > 0) { + const int nsPerMs = 1000000; + if (timeout < nsPerMs) { + timeoutMs = 1; + } else { + timeoutMs = static_cast<int>( + std::chrono::round<std::chrono::milliseconds>(std::chrono::nanoseconds{timeout}) + .count()); } } + epoll_event event{}; + int eventCount; + do { + eventCount = epoll_wait(mEpollFd.get(), &event, 1 /*maxevents*/, timeoutMs); + } while (eventCount == -1 && errno != EINTR); + + if (eventCount == -1) { + ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno, + strerror(errno)); + return UNKNOWN_ERROR; + } + + if (eventCount == 0) { + return TIMED_OUT; + } + if (event.data.fd == mEventFd.get()) { - uint64_t value; - if (read(mEventFd.get(), &value, sizeof(uint64_t)) == -1 && errno != EWOULDBLOCK) { - ALOGE("error while reading from eventfd. errno=%d message='%s'", errno, - strerror(errno)); - } + clearInterrupts(); return WOULD_BLOCK; } - std::lock_guard lock{mMutex}; - return mEndpoint->readReleaseFence(outId, outFence, outMaxAcquiredBufferCount); + return mBbq.mBufferReleaseConsumer->readReleaseFence(outId, outFence, + outMaxAcquiredBufferCount); } void BLASTBufferQueue::BufferReleaseReader::interruptBlockingRead() { - uint64_t value = 1; - if (write(mEventFd.get(), &value, sizeof(uint64_t)) == -1) { + if (eventfd_write(mEventFd.get(), 1) == -1) { ALOGE("failed to notify dequeue event. errno=%d message='%s'", errno, strerror(errno)); } } -void BLASTBufferQueue::BufferReleaseThread::start(const sp<BLASTBufferQueue>& bbq) { - mRunning = std::make_shared<std::atomic_bool>(true); - mReader = bbq->mBufferReleaseReader; - std::thread([running = mRunning, reader = mReader, weakBbq = wp<BLASTBufferQueue>(bbq)]() { - pthread_setname_np(pthread_self(), "BufferReleaseThread"); - while (*running) { - ReleaseCallbackId id; - sp<Fence> fence; - uint32_t maxAcquiredBufferCount; - if (status_t status = reader->readBlocking(id, fence, maxAcquiredBufferCount); - status != OK) { - continue; - } - sp<BLASTBufferQueue> bbq = weakBbq.promote(); - if (!bbq) { - return; - } - bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount); - } - }).detach(); -} - -BLASTBufferQueue::BufferReleaseThread::~BufferReleaseThread() { - *mRunning = false; - mReader->interruptBlockingRead(); +void BLASTBufferQueue::BufferReleaseReader::clearInterrupts() { + eventfd_t value; + if (eventfd_read(mEventFd.get(), &value) == -1 && errno != EWOULDBLOCK) { + ALOGE("error while reading from eventfd. errno=%d message='%s'", errno, strerror(errno)); + } } #endif diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp index 5beba02e63..3b2d337a21 100644 --- a/libs/gui/BufferItem.cpp +++ b/libs/gui/BufferItem.cpp @@ -38,26 +38,25 @@ static inline constexpr T to64(const uint32_t lo, const uint32_t hi) { return static_cast<T>(static_cast<uint64_t>(hi)<<32 | lo); } -BufferItem::BufferItem() : - mGraphicBuffer(nullptr), - mFence(nullptr), - mCrop(Rect::INVALID_RECT), - mTransform(0), - mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), - mTimestamp(0), - mIsAutoTimestamp(false), - mDataSpace(HAL_DATASPACE_UNKNOWN), - mFrameNumber(0), - mSlot(INVALID_BUFFER_SLOT), - mIsDroppable(false), - mAcquireCalled(false), - mTransformToDisplayInverse(false), - mSurfaceDamage(), - mAutoRefresh(false), - mQueuedBuffer(true), - mIsStale(false), - mApi(0) { -} +BufferItem::BufferItem() + : mGraphicBuffer(nullptr), + mFence(nullptr), + mCrop(Rect::INVALID_RECT), + mTransform(0), + mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTimestamp(0), + mIsAutoTimestamp(false), + mDataSpace(HAL_DATASPACE_UNKNOWN), + mFrameNumber(0), + mSlot(INVALID_BUFFER_SLOT), + mIsDroppable(false), + mAcquireCalled(false), + mTransformToDisplayInverse(false), + mSurfaceDamage(), + mAutoRefresh(false), + mQueuedBuffer(true), + mIsStale(false), + mApi(0) {} BufferItem::~BufferItem() {} @@ -76,6 +75,11 @@ size_t BufferItem::getPodSize() const { addAligned(size, high32(mTimestamp)); addAligned(size, mIsAutoTimestamp); addAligned(size, mDataSpace); +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + addAligned(size, mPictureProfileHandle.has_value()); + addAligned(size, low32(PictureProfileHandle::NONE.getId())); + addAligned(size, high32(PictureProfileHandle::NONE.getId())); +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES addAligned(size, low32(mFrameNumber)); addAligned(size, high32(mFrameNumber)); addAligned(size, mSlot); @@ -170,6 +174,16 @@ status_t BufferItem::flatten( writeAligned(buffer, size, high32(mTimestamp)); writeAligned(buffer, size, mIsAutoTimestamp); writeAligned(buffer, size, mDataSpace); +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + writeAligned(buffer, size, mPictureProfileHandle.has_value()); + if (mPictureProfileHandle.has_value()) { + writeAligned(buffer, size, low32(mPictureProfileHandle->getId())); + writeAligned(buffer, size, high32(mPictureProfileHandle->getId())); + } else { + writeAligned(buffer, size, low32(PictureProfileHandle::NONE.getId())); + writeAligned(buffer, size, high32(PictureProfileHandle::NONE.getId())); + } +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES writeAligned(buffer, size, low32(mFrameNumber)); writeAligned(buffer, size, high32(mFrameNumber)); writeAligned(buffer, size, mSlot); @@ -231,6 +245,7 @@ status_t BufferItem::unflatten( uint32_t timestampLo = 0, timestampHi = 0; uint32_t frameNumberLo = 0, frameNumberHi = 0; + int32_t pictureProfileIdLo = 0, pictureProfileIdHi = 0; readAligned(buffer, size, mCrop); readAligned(buffer, size, mTransform); @@ -240,6 +255,16 @@ status_t BufferItem::unflatten( mTimestamp = to64<int64_t>(timestampLo, timestampHi); readAligned(buffer, size, mIsAutoTimestamp); readAligned(buffer, size, mDataSpace); +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + bool hasPictureProfileHandle; + readAligned(buffer, size, hasPictureProfileHandle); + readAligned(buffer, size, pictureProfileIdLo); + readAligned(buffer, size, pictureProfileIdHi); + mPictureProfileHandle = hasPictureProfileHandle + ? std::optional(PictureProfileHandle( + to64<PictureProfileId>(pictureProfileIdLo, pictureProfileIdHi))) + : std::nullopt; +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES readAligned(buffer, size, frameNumberLo); readAligned(buffer, size, frameNumberHi); mFrameNumber = to64<uint64_t>(frameNumberLo, frameNumberHi); diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp index bfe3d6e023..8566419435 100644 --- a/libs/gui/BufferItemConsumer.cpp +++ b/libs/gui/BufferItemConsumer.cpp @@ -140,7 +140,7 @@ status_t BufferItemConsumer::releaseBufferSlotLocked(int slotIndex, const sp<Gra BI_LOGE("Failed to addReleaseFenceLocked"); } - err = releaseBufferLocked(slotIndex, buffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR); + err = releaseBufferLocked(slotIndex, buffer); if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) { BI_LOGE("Failed to release buffer: %s (%d)", strerror(-err), err); diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp index 69d25be006..9855b5bca4 100644 --- a/libs/gui/BufferQueueConsumer.cpp +++ b/libs/gui/BufferQueueConsumer.cpp @@ -28,6 +28,10 @@ #define VALIDATE_CONSISTENCY() #endif +#define EGL_EGLEXT_PROTOTYPES +#include <EGL/egl.h> +#include <EGL/eglext.h> + #include <gui/BufferItem.h> #include <gui/BufferQueueConsumer.h> #include <gui/BufferQueueCore.h> @@ -297,7 +301,11 @@ status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, // We might have freed a slot while dropping old buffers, or the producer // may be blocked waiting for the number of buffers in the queue to // decrease. +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif ATRACE_INT(mCore->mConsumerName.c_str(), static_cast<int32_t>(mCore->mQueue.size())); #ifndef NO_BINDER @@ -350,7 +358,12 @@ status_t BufferQueueConsumer::detachBuffer(int slot) { mCore->mActiveBuffers.erase(slot); mCore->mFreeSlots.insert(slot); mCore->clearBufferSlotLocked(slot); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif + VALIDATE_CONSISTENCY(); } @@ -477,6 +490,27 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber, return BAD_VALUE; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) + if (eglFence != EGL_NO_SYNC_KHR) { + // Most platforms will be using native fences, so it's unlikely that we'll ever have to + // process an eglFence. Ideally we can remove this code eventually. In the mean time, do our + // best to wait for it so the buffer stays valid, otherwise return an error to the caller. + // + // EGL_SYNC_FLUSH_COMMANDS_BIT_KHR so that we don't wait forever on a fence that hasn't + // shown up on the GPU yet. + EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, + 1000000000); + if (result == EGL_FALSE) { + BQ_LOGE("releaseBuffer: error %#x waiting for fence", eglGetError()); + return UNKNOWN_ERROR; + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + BQ_LOGE("releaseBuffer: timeout waiting for fence"); + return UNKNOWN_ERROR; + } + eglDestroySyncKHR(eglDisplay, eglFence); + } +#endif + sp<IProducerListener> listener; { // Autolock scope std::lock_guard<std::mutex> lock(mCore->mMutex); @@ -498,8 +532,10 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber, return BAD_VALUE; } +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) mSlots[slot].mEglDisplay = eglDisplay; mSlots[slot].mEglFence = eglFence; +#endif mSlots[slot].mFence = releaseFence; mSlots[slot].mBufferState.release(); @@ -520,7 +556,12 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber, } BQ_LOGV("releaseBuffer: releasing slot %d", slot); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif + VALIDATE_CONSISTENCY(); } // Autolock scope @@ -574,7 +615,11 @@ status_t BufferQueueConsumer::disconnect() { mCore->mQueue.clear(); mCore->freeAllBuffersLocked(); mCore->mSharedBufferSlot = BufferQueueCore::INVALID_BUFFER_SLOT; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif return NO_ERROR; } diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp index e0c5b1f7d1..5a093995b4 100644 --- a/libs/gui/BufferQueueCore.cpp +++ b/libs/gui/BufferQueueCore.cpp @@ -262,14 +262,16 @@ void BufferQueueCore::clearBufferSlotLocked(int slot) { mSlots[slot].mFrameNumber = 0; mSlots[slot].mAcquireCalled = false; mSlots[slot].mNeedsReallocation = true; + mSlots[slot].mFence = Fence::NO_FENCE; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) // Destroy fence as BufferQueue now takes ownership if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) { eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence); mSlots[slot].mEglFence = EGL_NO_SYNC_KHR; } - mSlots[slot].mFence = Fence::NO_FENCE; mSlots[slot].mEglDisplay = EGL_NO_DISPLAY; +#endif if (mLastQueuedSlot == slot) { mLastQueuedSlot = INVALID_BUFFER_SLOT; @@ -371,6 +373,12 @@ void BufferQueueCore::waitWhileAllocatingLocked(std::unique_lock<std::mutex>& lo } } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) +void BufferQueueCore::notifyBufferReleased() const { + mDequeueCondition.notify_all(); +} +#endif + #if DEBUG_ONLY_CODE void BufferQueueCore::validateConsistencyLocked() const { static const useconds_t PAUSE_TIME = 0; diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 831b2ee4be..2e7cef0847 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -202,7 +202,11 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers, if (delta < 0) { listener = mCore->mConsumerListener; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif } // Autolock scope // Call back without lock held @@ -254,7 +258,12 @@ status_t BufferQueueProducer::setAsyncMode(bool async) { } mCore->mAsyncMode = async; VALIDATE_CONSISTENCY(); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif + if (delta < 0) { listener = mCore->mConsumerListener; } @@ -376,6 +385,12 @@ status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller, (acquiredCount <= mCore->mMaxAcquiredBufferCount)) { return WOULD_BLOCK; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + if (status_t status = waitForBufferRelease(lock, mDequeueTimeout); + status == TIMED_OUT) { + return TIMED_OUT; + } +#else if (mDequeueTimeout >= 0) { std::cv_status result = mCore->mDequeueCondition.wait_for(lock, std::chrono::nanoseconds(mDequeueTimeout)); @@ -385,12 +400,29 @@ status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller, } else { mCore->mDequeueCondition.wait(lock); } +#endif } } // while (tryAgain) return NO_ERROR; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) +status_t BufferQueueProducer::waitForBufferRelease(std::unique_lock<std::mutex>& lock, + nsecs_t timeout) const { + if (mDequeueTimeout >= 0) { + std::cv_status result = + mCore->mDequeueCondition.wait_for(lock, std::chrono::nanoseconds(timeout)); + if (result == std::cv_status::timeout) { + return TIMED_OUT; + } + } else { + mCore->mDequeueCondition.wait(lock); + } + return OK; +} +#endif + status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence, uint32_t width, uint32_t height, PixelFormat format, uint64_t usage, uint64_t* outBufferAge, @@ -419,8 +451,10 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou } status_t returnFlags = NO_ERROR; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) EGLDisplay eglDisplay = EGL_NO_DISPLAY; EGLSyncKHR eglFence = EGL_NO_SYNC_KHR; +#endif bool attachedByConsumer = false; sp<IConsumerListener> listener; @@ -537,8 +571,10 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou mSlots[found].mAcquireCalled = false; mSlots[found].mGraphicBuffer = nullptr; mSlots[found].mRequestBufferCalled = false; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) mSlots[found].mEglDisplay = EGL_NO_DISPLAY; mSlots[found].mEglFence = EGL_NO_SYNC_KHR; +#endif mSlots[found].mFence = Fence::NO_FENCE; mCore->mBufferAge = 0; mCore->mIsAllocating = true; @@ -563,14 +599,18 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou found, buffer->width, buffer->height, buffer->format); } +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) eglDisplay = mSlots[found].mEglDisplay; eglFence = mSlots[found].mEglFence; +#endif // Don't return a fence in shared buffer mode, except for the first // frame. *outFence = (mCore->mSharedBufferMode && mCore->mSharedBufferSlot == found) ? Fence::NO_FENCE : mSlots[found].mFence; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) mSlots[found].mEglFence = EGL_NO_SYNC_KHR; +#endif mSlots[found].mFence = Fence::NO_FENCE; // If shared buffer mode has just been enabled, cache the slot of the @@ -659,6 +699,7 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou returnFlags |= BUFFER_NEEDS_REALLOCATION; } +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) if (eglFence != EGL_NO_SYNC_KHR) { EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0, 1000000000); @@ -673,6 +714,7 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou } eglDestroySyncKHR(eglDisplay, eglFence); } +#endif BQ_LOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x", *outSlot, @@ -741,7 +783,11 @@ status_t BufferQueueProducer::detachBuffer(int slot) { mCore->mActiveBuffers.erase(slot); mCore->mFreeSlots.insert(slot); mCore->clearBufferSlotLocked(slot); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif VALIDATE_CONSISTENCY(); } @@ -872,7 +918,9 @@ status_t BufferQueueProducer::attachBuffer(int* outSlot, mSlots[*outSlot].mGraphicBuffer = buffer; mSlots[*outSlot].mBufferState.attachProducer(); +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR; +#endif mSlots[*outSlot].mFence = Fence::NO_FENCE; mSlots[*outSlot].mRequestBufferCalled = true; mSlots[*outSlot].mAcquireCalled = false; @@ -902,6 +950,8 @@ status_t BufferQueueProducer::queueBuffer(int slot, &getFrameTimestamps); const Region& surfaceDamage = input.getSurfaceDamage(); const HdrMetadata& hdrMetadata = input.getHdrMetadata(); + const std::optional<PictureProfileHandle>& pictureProfileHandle = + input.getPictureProfileHandle(); if (acquireFence == nullptr) { BQ_LOGE("queueBuffer: fence is NULL"); @@ -1008,6 +1058,7 @@ status_t BufferQueueProducer::queueBuffer(int slot, item.mIsAutoTimestamp = isAutoTimestamp; item.mDataSpace = dataSpace; item.mHdrMetadata = hdrMetadata; + item.mPictureProfileHandle = pictureProfileHandle; item.mFrameNumber = currentFrameNumber; item.mSlot = slot; item.mFence = acquireFence; @@ -1082,7 +1133,11 @@ status_t BufferQueueProducer::queueBuffer(int slot, } mCore->mBufferHasBeenQueued = true; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif mCore->mLastQueuedSlot = slot; output->width = mCore->mDefaultWidth; @@ -1218,7 +1273,11 @@ status_t BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) { bufferId = gb->getId(); } mSlots[slot].mFence = fence; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif listener = mCore->mConsumerListener; VALIDATE_CONSISTENCY(); } @@ -1457,7 +1516,11 @@ status_t BufferQueueProducer::disconnect(int api, DisconnectMode mode) { mCore->mConnectedApi = BufferQueueCore::NO_CONNECTED_API; mCore->mConnectedPid = -1; mCore->mSidebandStream.clear(); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + mCore->notifyBufferReleased(); +#else mCore->mDequeueCondition.notify_all(); +#endif mCore->mAutoPrerotation = false; #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE) mCore->mAdditionalOptions.clear(); diff --git a/libs/gui/BufferReleaseChannel.cpp b/libs/gui/BufferReleaseChannel.cpp index 27367aa83f..e9cb013baf 100644 --- a/libs/gui/BufferReleaseChannel.cpp +++ b/libs/gui/BufferReleaseChannel.cpp @@ -35,35 +35,35 @@ namespace android::gui { namespace { template <typename T> -static void readAligned(const void*& buffer, size_t& size, T& value) { +void readAligned(const void*& buffer, size_t& size, T& value) { size -= FlattenableUtils::align<alignof(T)>(buffer); FlattenableUtils::read(buffer, size, value); } template <typename T> -static void writeAligned(void*& buffer, size_t& size, T value) { +void writeAligned(void*& buffer, size_t& size, T value) { size -= FlattenableUtils::align<alignof(T)>(buffer); FlattenableUtils::write(buffer, size, value); } template <typename T> -static void addAligned(size_t& size, T /* value */) { +void addAligned(size_t& size, T /* value */) { size = FlattenableUtils::align<sizeof(T)>(size); size += sizeof(T); } template <typename T> -static inline constexpr uint32_t low32(const T n) { +inline constexpr uint32_t low32(const T n) { return static_cast<uint32_t>(static_cast<uint64_t>(n)); } template <typename T> -static inline constexpr uint32_t high32(const T n) { +inline constexpr uint32_t high32(const T n) { return static_cast<uint32_t>(static_cast<uint64_t>(n) >> 32); } template <typename T> -static inline constexpr T to64(const uint32_t lo, const uint32_t hi) { +inline constexpr T to64(const uint32_t lo, const uint32_t hi) { return static_cast<T>(static_cast<uint64_t>(hi) << 32 | lo); } @@ -136,23 +136,23 @@ status_t BufferReleaseChannel::Message::unflatten(void const*& buffer, size_t& s status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence( ReleaseCallbackId& outReleaseCallbackId, sp<Fence>& outReleaseFence, uint32_t& outMaxAcquiredBufferCount) { + std::lock_guard lock{mMutex}; Message message; mFlattenedBuffer.resize(message.getFlattenedSize()); - std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer; + std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{}; iovec iov{ .iov_base = mFlattenedBuffer.data(), .iov_len = mFlattenedBuffer.size(), }; - msghdr msg{ - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = controlMessageBuffer.data(), - .msg_controllen = controlMessageBuffer.size(), - }; + msghdr msg{}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = controlMessageBuffer.data(); + msg.msg_controllen = controlMessageBuffer.size(); - int result; + ssize_t result; do { result = recvmsg(mFd, &msg, 0); } while (result == -1 && errno == EINTR); @@ -160,7 +160,7 @@ status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence( if (errno == EWOULDBLOCK || errno == EAGAIN) { return WOULD_BLOCK; } - ALOGE("Error reading release fence from socket: error %#x (%s)", errno, strerror(errno)); + ALOGE("Error reading release fence from socket: error %d (%s)", errno, strerror(errno)); return UNKNOWN_ERROR; } @@ -199,9 +199,9 @@ status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence( return OK; } -int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallbackId& callbackId, - const sp<Fence>& fence, - uint32_t maxAcquiredBufferCount) { +status_t BufferReleaseChannel::ProducerEndpoint::writeReleaseFence( + const ReleaseCallbackId& callbackId, const sp<Fence>& fence, + uint32_t maxAcquiredBufferCount) { Message message{callbackId, fence ? fence : Fence::NO_FENCE, maxAcquiredBufferCount}; mFlattenedBuffer.resize(message.getFlattenedSize()); int flattenedFd; @@ -212,25 +212,22 @@ int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallb size_t flattenedBufferSize = mFlattenedBuffer.size(); int* flattenedFdPtr = &flattenedFd; size_t flattenedFdCount = 1; - if (status_t err = message.flatten(flattenedBufferPtr, flattenedBufferSize, flattenedFdPtr, - flattenedFdCount); - err != OK) { - ALOGE("Failed to flatten BufferReleaseChannel message."); - return err; + if (status_t status = message.flatten(flattenedBufferPtr, flattenedBufferSize, + flattenedFdPtr, flattenedFdCount); + status != OK) { + return status; } } - iovec iov{ - .iov_base = mFlattenedBuffer.data(), - .iov_len = mFlattenedBuffer.size(), - }; + iovec iov{}; + iov.iov_base = mFlattenedBuffer.data(); + iov.iov_len = mFlattenedBuffer.size(); - msghdr msg{ - .msg_iov = &iov, - .msg_iovlen = 1, - }; + msghdr msg{}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; - std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer; + std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{}; if (fence && fence->isValid()) { msg.msg_control = controlMessageBuffer.data(); msg.msg_controllen = controlMessageBuffer.size(); @@ -242,12 +239,11 @@ int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallb memcpy(CMSG_DATA(cmsg), &flattenedFd, sizeof(int)); } - int result; + ssize_t result; do { result = sendmsg(mFd, &msg, 0); } while (result == -1 && errno == EINTR); if (result == -1) { - ALOGD("Error writing release fence to socket: error %#x (%s)", errno, strerror(errno)); return -errno; } @@ -343,13 +339,6 @@ status_t BufferReleaseChannel::open(std::string name, return -errno; } - // Make the producer write-only - if (shutdown(producerFd.get(), SHUT_RD) == -1) { - ALOGE("[%s] Failed to shutdown reading on producer socket. errno=%d message='%s'", - name.c_str(), errno, strerror(errno)); - return -errno; - } - outConsumer = std::make_unique<ConsumerEndpoint>(name, std::move(consumerFd)); outProducer = std::make_shared<ProducerEndpoint>(std::move(name), std::move(producerFd)); return STATUS_OK; diff --git a/libs/gui/BufferStuffing.md b/libs/gui/BufferStuffing.md new file mode 100644 index 0000000000..6ed8ad988b --- /dev/null +++ b/libs/gui/BufferStuffing.md @@ -0,0 +1,7 @@ +### Buffer Stuffing and Recovery ### + +Buffer stuffing happens on the client side when SurfaceFlinger misses a frame, but the client continues producing buffers at the same rate. This could occur anytime when SurfaceFlinger does not meet the expected timeline’s deadline to finish composing a frame. As a result, SurfaceFlinger cannot yet release the buffer for the frame that it missed and the client has one less buffer to render into. The client may then run out of buffers or have to wait for buffer release callbacks, increasing the chances of janking when clients render multiple windows. + +Recovery is implemented by first detecting when buffer stuffing occurs and ensuring that the elevated buffer counts in the server are from a relevant SurfaceControl (is a ViewRootImpl). Other SurfaceControl buffer producers such as games, media, and camera have other reasons for expectedly increased buffer counts, which do not need buffer stuffing recovery. + +The actual recovery adjusts the animation timeline in the Choreographer so that the client deadlines for subsequent frames are moved forward in time by one frame. This approach adjusts the client buffer production timeline such that SurfaceFlinger does not fall behind when it misses a frame because the client will simply match its frame production rate with SurfaceFlinger. Ordinarily, buffer stuffing is problematic because the client continues producing buffers when SurfaceFlinger is behind. However, if the client delays producing its buffers to match SurfaceFlinger’s rate, the animation has new frame deadlines that can be reasonably met. The animation is effectively paused for one frame longer than originally intended, and continues the remainder of the animation normally.
\ No newline at end of file diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 0c8f3fa096..ba50bf83a8 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -369,6 +369,10 @@ void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32 this, to_string(displayId).c_str(), connectedLevel, maxLevel); } +void Choreographer::dispatchModeRejected(PhysicalDisplayId, int32_t) { + LOG_ALWAYS_FATAL("dispatchModeRejected was called but was never registered"); +} + void Choreographer::handleMessage(const Message& message) { switch (message.what) { case MSG_SCHEDULE_CALLBACKS: diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index c46f9c50ef..68f10f4d80 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -211,6 +211,9 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp, ev.hdcpLevelsChange.connectedLevel, ev.hdcpLevelsChange.maxLevel); break; + case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION: + dispatchModeRejected(ev.header.displayId, ev.modeRejection.modeId); + break; default: ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type); break; diff --git a/libs/gui/DisplayLuts.cpp b/libs/gui/DisplayLuts.cpp new file mode 100644 index 0000000000..80429765be --- /dev/null +++ b/libs/gui/DisplayLuts.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 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 "include/gui/DisplayLuts.h" +#include <gui/DisplayLuts.h> +#include <private/gui/ParcelUtils.h> + +namespace android::gui { + +status_t DisplayLuts::Entry::readFromParcel(const android::Parcel* parcel) { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->readInt32, &dimension); + SAFE_PARCEL(parcel->readInt32, &size); + SAFE_PARCEL(parcel->readInt32, &samplingKey); + + return OK; +} + +status_t DisplayLuts::Entry::writeToParcel(android::Parcel* parcel) const { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->writeInt32, dimension); + SAFE_PARCEL(parcel->writeInt32, size); + SAFE_PARCEL(parcel->writeInt32, samplingKey); + + return OK; +} + +status_t DisplayLuts::readFromParcel(const android::Parcel* parcel) { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->readUniqueFileDescriptor, &fd); + SAFE_PARCEL(parcel->readInt32Vector, &offsets); + int32_t numLutProperties; + SAFE_PARCEL(parcel->readInt32, &numLutProperties); + lutProperties.reserve(numLutProperties); + for (int32_t i = 0; i < numLutProperties; i++) { + lutProperties.push_back({}); + SAFE_PARCEL(lutProperties.back().readFromParcel, parcel); + } + return OK; +} + +status_t DisplayLuts::writeToParcel(android::Parcel* parcel) const { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->writeUniqueFileDescriptor, fd); + SAFE_PARCEL(parcel->writeInt32Vector, offsets); + SAFE_PARCEL(parcel->writeInt32, static_cast<int32_t>(lutProperties.size())); + for (auto& entry : lutProperties) { + SAFE_PARCEL(entry.writeToParcel, parcel); + } + return OK; +} +} // namespace android::gui
\ No newline at end of file diff --git a/libs/gui/Flags.cpp b/libs/gui/Flags.cpp new file mode 100644 index 0000000000..85ee2cddad --- /dev/null +++ b/libs/gui/Flags.cpp @@ -0,0 +1,73 @@ +/* + * 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 <gui/Flags.h> +#include <gui/IGraphicBufferProducer.h> +#include <gui/Surface.h> +#include <gui/view/Surface.h> + +namespace android { +namespace flagtools { +sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return surface; +#else + return surface->getIGraphicBufferProducer(); +#endif +} + +sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return surface->getIGraphicBufferProducer(); +#else + return surface; +#endif +} + +bool isSurfaceTypeValid(const sp<SurfaceType>& surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return Surface::isValid(surface); +#else + return surface != nullptr; +#endif +} + +ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return surface; +#else + return surface.graphicBufferProducer; +#endif +} + +ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return view::Surface::fromSurface(surface); +#else + return surface; +#endif +} + +sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface) { +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + return surface.toSurface(); +#else + return surface; +#endif +} + +} // namespace flagtools +} // namespace android
\ No newline at end of file diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp index 01aa7ed43c..5c4879c1bd 100644 --- a/libs/gui/FrameRateUtils.cpp +++ b/libs/gui/FrameRateUtils.cpp @@ -42,7 +42,7 @@ bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrame if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT && compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE && - compatibility != ANATIVEWINDOW_FRAME_RATE_GTE && + compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE && (!privileged || (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT && compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) { diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp index 95cce5c1df..f2173cd740 100644 --- a/libs/gui/GLConsumer.cpp +++ b/libs/gui/GLConsumer.cpp @@ -314,7 +314,7 @@ status_t GLConsumer::releaseTexImage() { // so... basically, nothing more to do here. } - err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer); if (err < NO_ERROR) { GLC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err); @@ -418,16 +418,14 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item, if (!mAttached) { GLC_LOGE("updateAndRelease: GLConsumer is not attached to an OpenGL " "ES context"); - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer, - mEglDisplay, EGL_NO_SYNC_KHR); + releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); return INVALID_OPERATION; } // Confirm state. err = checkAndUpdateEglStateLocked(); if (err != NO_ERROR) { - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer, - mEglDisplay, EGL_NO_SYNC_KHR); + releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); return err; } @@ -440,8 +438,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item, if (err != NO_ERROR) { GLC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay, slot); - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer, - mEglDisplay, EGL_NO_SYNC_KHR); + releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); return UNKNOWN_ERROR; } @@ -453,8 +450,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item, // release the old buffer, so instead we just drop the new frame. // As we are still under lock since acquireBuffer, it is safe to // release by slot. - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer, - mEglDisplay, EGL_NO_SYNC_KHR); + releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); return err; } } diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp index c705d3926d..282957b940 100644 --- a/libs/gui/IGraphicBufferConsumer.cpp +++ b/libs/gui/IGraphicBufferConsumer.cpp @@ -26,6 +26,7 @@ #include <utils/NativeHandle.h> #include <utils/String8.h> +#include <cstdint> namespace android { @@ -84,7 +85,8 @@ public: EGLDisplay display __attribute__((unused)), EGLSyncKHR fence __attribute__((unused)), const sp<Fence>& releaseFence) override { - return callRemote<ReleaseBuffer>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence); + using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&); + return callRemote<Signature>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence); } status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override { @@ -188,8 +190,10 @@ status_t BnGraphicBufferConsumer::onTransact(uint32_t code, const Parcel& data, return callLocal(data, reply, &IGraphicBufferConsumer::detachBuffer); case Tag::ATTACH_BUFFER: return callLocal(data, reply, &IGraphicBufferConsumer::attachBuffer); - case Tag::RELEASE_BUFFER: - return callLocal(data, reply, &IGraphicBufferConsumer::releaseHelper); + case Tag::RELEASE_BUFFER: { + using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&); + return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::releaseBuffer); + } case Tag::CONSUMER_CONNECT: return callLocal(data, reply, &IGraphicBufferConsumer::consumerConnect); case Tag::CONSUMER_DISCONNECT: diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp index c8b9b6751d..4e92a39973 100644 --- a/libs/gui/IGraphicBufferProducerFlattenables.cpp +++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp @@ -20,21 +20,19 @@ namespace android { constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() { - return sizeof(timestamp) + - sizeof(isAutoTimestamp) + - sizeof(dataSpace) + - sizeof(crop) + - sizeof(scalingMode) + - sizeof(transform) + - sizeof(stickyTransform) + - sizeof(getFrameTimestamps) + - sizeof(slot); + return sizeof(timestamp) + sizeof(isAutoTimestamp) + sizeof(dataSpace) + sizeof(crop) + + sizeof(scalingMode) + sizeof(transform) + sizeof(stickyTransform) + + sizeof(getFrameTimestamps) + sizeof(slot) + +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + sizeof(decltype(pictureProfileHandle.has_value())) + + sizeof(decltype(pictureProfileHandle.getId())); +#else + 0; +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES } size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const { - return minFlattenedSize() + - fence->getFlattenedSize() + - surfaceDamage.getFlattenedSize() + + return minFlattenedSize() + fence->getFlattenedSize() + surfaceDamage.getFlattenedSize() + hdrMetadata.getFlattenedSize(); } @@ -57,6 +55,12 @@ status_t IGraphicBufferProducer::QueueBufferInput::flatten( FlattenableUtils::write(buffer, size, transform); FlattenableUtils::write(buffer, size, stickyTransform); FlattenableUtils::write(buffer, size, getFrameTimestamps); +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + FlattenableUtils::write(buffer, size, pictureProfileHandle.has_value()); + FlattenableUtils::write(buffer, size, + pictureProfileHandle.has_value() ? pictureProfileHandle->getId() + : PictureProfileHandle::NONE.getId()); +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES status_t result = fence->flatten(buffer, size, fds, count); if (result != NO_ERROR) { @@ -91,6 +95,15 @@ status_t IGraphicBufferProducer::QueueBufferInput::unflatten( FlattenableUtils::read(buffer, size, transform); FlattenableUtils::read(buffer, size, stickyTransform); FlattenableUtils::read(buffer, size, getFrameTimestamps); +#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES + bool hasPictureProfileHandle; + FlattenableUtils::read(buffer, size, hasPictureProfileHandle); + PictureProfileId pictureProfileId; + FlattenableUtils::read(buffer, size, pictureProfileId); + pictureProfileHandle = hasPictureProfileHandle + ? std::optional(PictureProfileHandle(pictureProfileId)) + : std::nullopt; +#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES fence = new Fence(); status_t result = fence->unflatten(buffer, size, fds, count); diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index b10996951b..c1a03fcfea 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -21,6 +21,7 @@ #include <android/gui/ISurfaceComposerClient.h> #include <android/native_window.h> #include <binder/Parcel.h> +#include <com_android_graphics_libgui_flags.h> #include <gui/FrameRateUtils.h> #include <gui/IGraphicBufferProducer.h> #include <gui/LayerState.h> @@ -69,7 +70,7 @@ layer_state_t::layer_state_t() color(0), bufferTransform(0), transformToDisplayInverse(false), - crop(Rect::INVALID_RECT), + crop({0, 0, -1, -1}), dataspace(ui::Dataspace::UNKNOWN), surfaceDamageRegion(), api(-1), @@ -91,7 +92,9 @@ layer_state_t::layer_state_t() trustedOverlay(gui::TrustedOverlay::UNSET), bufferCrop(Rect::INVALID_RECT), destinationFrame(Rect::INVALID_RECT), - dropInputMode(gui::DropInputMode::NONE) { + dropInputMode(gui::DropInputMode::NONE), + pictureProfileHandle(PictureProfileHandle::NONE), + appContentPriority(0) { matrix.dsdx = matrix.dtdy = 1.0f; matrix.dsdy = matrix.dtdx = 0.0f; hdrMetadata.validTypes = 0; @@ -109,7 +112,10 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeUint32, flags); SAFE_PARCEL(output.writeUint32, mask); SAFE_PARCEL(matrix.write, output); - SAFE_PARCEL(output.write, crop); + SAFE_PARCEL(output.writeFloat, crop.top); + SAFE_PARCEL(output.writeFloat, crop.left); + SAFE_PARCEL(output.writeFloat, crop.bottom); + SAFE_PARCEL(output.writeFloat, crop.right); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild); SAFE_PARCEL(output.writeFloat, color.r); @@ -199,6 +205,16 @@ status_t layer_state_t::write(Parcel& output) const if (hasBufferReleaseChannel) { SAFE_PARCEL(output.writeParcelable, *bufferReleaseChannel); } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES + SAFE_PARCEL(output.writeInt64, pictureProfileHandle.getId()); + SAFE_PARCEL(output.writeInt32, appContentPriority); +#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES + + const bool hasLuts = (luts != nullptr); + SAFE_PARCEL(output.writeBool, hasLuts); + if (hasLuts) { + SAFE_PARCEL(output.writeParcelable, *luts); + } return NO_ERROR; } @@ -218,7 +234,10 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readUint32, &mask); SAFE_PARCEL(matrix.read, input); - SAFE_PARCEL(input.read, crop); + SAFE_PARCEL(input.readFloat, &crop.top); + SAFE_PARCEL(input.readFloat, &crop.left); + SAFE_PARCEL(input.readFloat, &crop.bottom); + SAFE_PARCEL(input.readFloat, &crop.right); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild); @@ -351,6 +370,21 @@ status_t layer_state_t::read(const Parcel& input) bufferReleaseChannel = std::make_shared<gui::BufferReleaseChannel::ProducerEndpoint>(); SAFE_PARCEL(input.readParcelable, bufferReleaseChannel.get()); } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES + int64_t pictureProfileId; + SAFE_PARCEL(input.readInt64, &pictureProfileId); + pictureProfileHandle = PictureProfileHandle(pictureProfileId); + SAFE_PARCEL(input.readInt32, &appContentPriority); +#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES + + bool hasLuts; + SAFE_PARCEL(input.readBool, &hasLuts); + if (hasLuts) { + luts = std::make_shared<gui::DisplayLuts>(); + SAFE_PARCEL(input.readParcelable, luts.get()); + } else { + luts = nullptr; + } return NO_ERROR; } @@ -658,6 +692,10 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eShadowRadiusChanged; shadowRadius = other.shadowRadius; } + if (other.what & eLutsChanged) { + what |= eLutsChanged; + luts = other.luts; + } if (other.what & eDefaultFrameRateCompatibilityChanged) { what |= eDefaultFrameRateCompatibilityChanged; defaultFrameRateCompatibility = other.defaultFrameRateCompatibility; @@ -735,6 +773,16 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eBufferReleaseChannelChanged; bufferReleaseChannel = other.bufferReleaseChannel; } + if (com_android_graphics_libgui_flags_apply_picture_profiles()) { + if (other.what & ePictureProfileHandleChanged) { + what |= ePictureProfileHandleChanged; + pictureProfileHandle = other.pictureProfileHandle; + } + if (other.what & eAppContentPriorityChanged) { + what |= eAppContentPriorityChanged; + appContentPriority = other.appContentPriority; + } + } if ((other.what & what) != other.what) { ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? " "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64, @@ -815,6 +863,10 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic); CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled); if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged; + if (other.what & eLutsChanged) diff |= eLutsChanged; + CHECK_DIFF(diff, ePictureProfileHandleChanged, other, pictureProfileHandle); + CHECK_DIFF(diff, eAppContentPriorityChanged, other, appContentPriority); + return diff; } diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp index 2f8e104ea0..653b91bcf6 100644 --- a/libs/gui/StreamSplitter.cpp +++ b/libs/gui/StreamSplitter.cpp @@ -234,8 +234,7 @@ void StreamSplitter::onBufferReleasedByOutput( LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "attaching buffer to input failed (%d)", status); - status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence()); + status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, tracker->getMergedFence()); LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "releasing buffer to input failed (%d)", status); diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 66e7ddd915..e41f9bbf43 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -2735,8 +2735,8 @@ status_t Surface::unlockAndPost() bool Surface::waitForNextFrame(uint64_t lastFrame, nsecs_t timeout) { Mutex::Autolock lock(mMutex); - if (mNextFrameNumber > lastFrame) { - return true; + if (mLastFrameNumber > lastFrame) { + return true; } return mQueueBufferCondition.waitRelative(mMutex, timeout) == OK; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index df58df43be..be88b11b9c 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -20,8 +20,6 @@ #include <stdint.h> #include <sys/types.h> -#include <com_android_graphics_libgui_flags.h> - #include <android/gui/BnWindowInfosReportedListener.h> #include <android/gui/DisplayState.h> #include <android/gui/EdgeExtensionParameters.h> @@ -29,6 +27,8 @@ #include <android/gui/IWindowInfosListener.h> #include <android/gui/TrustedPresentationThresholds.h> #include <android/os/IInputConstants.h> +#include <com_android_graphics_libgui_flags.h> +#include <gui/DisplayLuts.h> #include <gui/FrameRateUtils.h> #include <gui/TraceUtils.h> #include <utils/Errors.h> @@ -56,6 +56,7 @@ #include <ui/DisplayMode.h> #include <ui/DisplayState.h> #include <ui/DynamicDisplayInfo.h> +#include <ui/FrameRateCategoryRate.h> #include <android-base/thread_annotations.h> #include <gui/LayerStatePermissions.h> @@ -90,6 +91,7 @@ int64_t generateId() { } constexpr int64_t INVALID_VSYNC = -1; +const constexpr char* LOG_SURFACE_CONTROL_REGISTRY = "SurfaceControlRegistry"; } // namespace @@ -871,6 +873,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel const bool earlyWakeupEnd = parcel->readBool(); const int64_t desiredPresentTime = parcel->readInt64(); const bool isAutoTimestamp = parcel->readBool(); + const bool logCallPoints = parcel->readBool(); FrameTimelineInfo frameTimelineInfo; frameTimelineInfo.readFromParcel(parcel); @@ -998,6 +1001,7 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const parcel->writeBool(mEarlyWakeupEnd); parcel->writeInt64(mDesiredPresentTime); parcel->writeBool(mIsAutoTimestamp); + parcel->writeBool(mLogCallPoints); mFrameTimelineInfo.writeToParcel(parcel); parcel->writeStrongBinder(mApplyToken); parcel->writeUint32(static_cast<uint32_t>(mDisplayStates.size())); @@ -1133,6 +1137,12 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo); + mLogCallPoints |= other.mLogCallPoints; + if (mLogCallPoints) { + ALOG(LOG_DEBUG, LOG_SURFACE_CONTROL_REGISTRY, + "Transaction %" PRIu64 " merged with transaction %" PRIu64, other.getId(), mId); + } + other.clear(); return *this; } @@ -1152,6 +1162,7 @@ void SurfaceComposerClient::Transaction::clear() { mFrameTimelineInfo = {}; mApplyToken = nullptr; mMergedTransactionIds.clear(); + mLogCallPoints = false; } uint64_t SurfaceComposerClient::Transaction::getId() { @@ -1346,21 +1357,26 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken(); sp<ISurfaceComposer> sf(ComposerService::getComposerService()); - sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, - mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId, - mMergedTransactionIds); + status_t binderStatus = + sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, + applyToken, mInputWindowCommands, mDesiredPresentTime, + mIsAutoTimestamp, mUncacheBuffers, hasListenerCallbacks, + listenerCallbacks, mId, mMergedTransactionIds); mId = generateId(); // Clear the current states and flags clear(); - if (synchronous) { + if (synchronous && binderStatus == OK) { syncCallback->wait(); } + if (mLogCallPoints) { + ALOG(LOG_DEBUG, LOG_SURFACE_CONTROL_REGISTRY, "Transaction %" PRIu64 " applied", mId); + } + mStatus = NO_ERROR; - return NO_ERROR; + return binderStatus; } sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder(); @@ -1374,7 +1390,7 @@ sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() { void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) { std::scoped_lock lock{sApplyTokenMutex}; - sApplyToken = applyToken; + sApplyToken = std::move(applyToken); } status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction( @@ -1389,6 +1405,11 @@ status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction t.registerSurfaceControlForCallback(sc); return t.apply(/*sync=*/false, /* oneWay=*/true); } + +void SurfaceComposerClient::Transaction::enableDebugLogCallPoints() { + mLogCallPoints = true; +} + // --------------------------------------------------------------------------- sp<IBinder> SurfaceComposerClient::createVirtualDisplay(const std::string& displayName, @@ -1539,14 +1560,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFlags mStatus = BAD_INDEX; return *this; } - if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) || - (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) || - (mask & layer_state_t::eEnableBackpressure) || - (mask & layer_state_t::eIgnoreDestinationFrame) || - (mask & layer_state_t::eLayerIsDisplayDecoration) || - (mask & layer_state_t::eLayerIsRefreshRateIndicator)) { - s->what |= layer_state_t::eFlagsChanged; - } + s->what |= layer_state_t::eFlagsChanged; s->flags &= ~mask; s->flags |= (flags & mask); s->mask |= mask; @@ -1652,6 +1666,11 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setMatri SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop( const sp<SurfaceControl>& sc, const Rect& crop) { + return setCrop(sc, crop.toFloatRect()); +} + +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop( + const sp<SurfaceControl>& sc, const FloatRect& crop) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -1941,6 +1960,28 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesir return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLuts( + const sp<SurfaceControl>& sc, const base::unique_fd& lutFd, + const std::vector<int32_t>& offsets, const std::vector<int32_t>& dimensions, + const std::vector<int32_t>& sizes, const std::vector<int32_t>& samplingKeys) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + s->what |= layer_state_t::eLutsChanged; + if (lutFd.ok()) { + s->luts = std::make_shared<gui::DisplayLuts>(base::unique_fd(dup(lutFd.get())), offsets, + dimensions, sizes, samplingKeys); + } else { + s->luts = nullptr; + } + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint( const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) { layer_state_t* s = getLayerState(sc); @@ -2091,13 +2132,13 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::notifyPr } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo( - const sp<SurfaceControl>& sc, const WindowInfo& info) { + const sp<SurfaceControl>& sc, sp<WindowInfoHandle> info) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; return *this; } - s->windowInfoHandle = new WindowInfoHandle(info); + s->windowInfoHandle = std::move(info); s->what |= layer_state_t::eInputInfoChanged; return *this; } @@ -2409,6 +2450,40 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPictureProfileHandle( + const sp<SurfaceControl>& sc, const PictureProfileHandle& pictureProfileHandle) { + if (com_android_graphics_libgui_flags_apply_picture_profiles()) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + s->what |= layer_state_t::ePictureProfileHandleChanged; + s->pictureProfileHandle = pictureProfileHandle; + + registerSurfaceControlForCallback(sc); + } + return *this; +} + +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setContentPriority( + const sp<SurfaceControl>& sc, int32_t priority) { + if (com_android_graphics_libgui_flags_apply_picture_profiles()) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + s->what |= layer_state_t::eAppContentPriorityChanged; + s->appContentPriority = priority; + + registerSurfaceControlForCallback(sc); + } + return *this; +} + // --------------------------------------------------------------------------- DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) { @@ -2795,6 +2870,14 @@ void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInf outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported; outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; + outInfo->hasArrSupport = ginfo.hasArrSupport; + outInfo->frameRateCategoryRate = ui::FrameRateCategoryRate(ginfo.frameRateCategoryRate.normal, + ginfo.frameRateCategoryRate.high); + outInfo->supportedRefreshRates.clear(); + outInfo->supportedRefreshRates.reserve(ginfo.supportedRefreshRates.size()); + for (const auto rate : ginfo.supportedRefreshRates) { + outInfo->supportedRefreshRates.push_back(static_cast<float>(rate)); + } } status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId, @@ -2978,6 +3061,14 @@ void SurfaceComposerClient::setDisplayPowerMode(const sp<IBinder>& token, ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode); } +status_t SurfaceComposerClient::getMaxLayerPictureProfiles(const sp<IBinder>& token, + int32_t* outMaxProfiles) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getMaxLayerPictureProfiles(token, + outMaxProfiles); + return statusTFromBinderStatus(status); +} + status_t SurfaceComposerClient::getCompositionPreference( ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat, ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) { @@ -3202,6 +3293,13 @@ status_t SurfaceComposerClient::removeHdrLayerInfoListener( return statusTFromBinderStatus(status); } +status_t SurfaceComposerClient::setActivePictureListener( + const sp<gui::IActivePictureListener>& listener) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->setActivePictureListener(listener); + return statusTFromBinderStatus(status); +} + status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) { binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId); return statusTFromBinderStatus(status); diff --git a/libs/gui/aidl/android/gui/ActivePicture.aidl b/libs/gui/aidl/android/gui/ActivePicture.aidl new file mode 100644 index 0000000000..9b83be16ca --- /dev/null +++ b/libs/gui/aidl/android/gui/ActivePicture.aidl @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package android.gui; + +/** + * Visible content that is using picture processing. + * @hide + */ +parcelable ActivePicture { + /** The layer ID that is using picture processing. */ + int layerId; + + /** UID that owns layer using picture processing. */ + int ownerUid; + + /** ID of the picture profile that was used to configure the picture processing. */ + long pictureProfileId; +} diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl index 3114929e86..26c12c56f3 100644 --- a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl +++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl @@ -17,6 +17,7 @@ package android.gui; import android.gui.DisplayMode; +import android.gui.FrameRateCategoryRate; import android.gui.HdrCapabilities; // Information about a physical display which may change on hotplug reconnect. @@ -43,4 +44,13 @@ parcelable DynamicDisplayInfo { // The boot display mode preferred by the implementation. int preferredBootDisplayMode; + + // Represents whether display supports ARR. + boolean hasArrSupport; + + // Represents frame rate for FrameRateCategory Normal and High. + FrameRateCategoryRate frameRateCategoryRate; + + // All the refresh rates supported for the default display mode. + float[] supportedRefreshRates; } diff --git a/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl b/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl new file mode 100644 index 0000000000..f30280139f --- /dev/null +++ b/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package android.gui; + +/** @hide */ +// Represents frame rate for FrameRateCategory Normal and High. +parcelable FrameRateCategoryRate { + float normal; + float high; +}
\ No newline at end of file diff --git a/libs/gui/aidl/android/gui/IActivePictureListener.aidl b/libs/gui/aidl/android/gui/IActivePictureListener.aidl new file mode 100644 index 0000000000..ad310bbd66 --- /dev/null +++ b/libs/gui/aidl/android/gui/IActivePictureListener.aidl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package android.gui; + +import android.gui.ActivePicture; + +/** + * Receive callbacks whenever the visible content using picture profiles changes. + * @hide + */ +interface IActivePictureListener { + /** + * Callback reporting the visible content on the screen using picture profiles. + */ + oneway void onActivePicturesChanged(in ActivePicture[] activePictures); +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index ac14138e8c..8c19bbbba9 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -33,6 +33,7 @@ import android.gui.FrameEvent; import android.gui.FrameStats; import android.gui.HdrConversionCapability; import android.gui.HdrConversionStrategy; +import android.gui.IActivePictureListener; import android.gui.IDisplayEventConnection; import android.gui.IFpsListener; import android.gui.IHdrLayerInfoListener; @@ -228,6 +229,11 @@ interface ISurfaceComposer { void setGameContentType(IBinder display, boolean on); /** + * Gets the maximum number of picture profiles supported by the display. + */ + int getMaxLayerPictureProfiles(IBinder display); + + /** * Capture the specified screen. This requires READ_FRAME_BUFFER * permission. This function will fail if there is a secure window on * screen and DisplayCaptureArgs.captureSecureLayers is false. @@ -599,4 +605,10 @@ interface ISurfaceComposer { * past the provided VSync. */ oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync); + + /** + * Sets the listener used to monitor visible content that is being processed with picture + * profiles. + */ + oneway void setActivePictureListener(IActivePictureListener listener); } diff --git a/libs/gui/aidl/android/gui/LutProperties.aidl b/libs/gui/aidl/android/gui/LutProperties.aidl new file mode 100644 index 0000000000..84c7013cda --- /dev/null +++ b/libs/gui/aidl/android/gui/LutProperties.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 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. + */ + +package android.gui; + +/** + * This mirrors aidl::android::hardware::graphics::composer3::LutProperties definition. + * @hide + */ +parcelable LutProperties { + @Backing(type="int") + enum Dimension { ONE_D = 1, THREE_D = 3 } + Dimension dimension; + + int size; + @Backing(type="int") + enum SamplingKey { RGB, MAX_RGB, CIE_Y } + SamplingKey[] samplingKeys; +}
\ No newline at end of file diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl index 5fb1a83c65..7f41bdab2f 100644 --- a/libs/gui/aidl/android/gui/OverlayProperties.aidl +++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl @@ -16,6 +16,8 @@ package android.gui; +import android.gui.LutProperties; + /** @hide */ parcelable OverlayProperties { parcelable SupportedBufferCombinations { @@ -27,4 +29,6 @@ parcelable OverlayProperties { SupportedBufferCombinations[] combinations; boolean supportMixedColorSpaces; + + @nullable LutProperties[] lutProperties; } diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 8592cffd15..07558aa49d 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -17,7 +17,9 @@ #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H -#include <com_android_graphics_libgui_flags.h> +#include <optional> +#include <queue> + #include <gui/BufferItem.h> #include <gui/BufferItemConsumer.h> #include <gui/IGraphicBufferConsumer.h> @@ -29,7 +31,6 @@ #include <utils/RefBase.h> #include <system/window.h> -#include <queue> #include <com_android_graphics_libgui_flags.h> @@ -150,6 +151,9 @@ public: private: friend class BLASTBufferQueueHelper; friend class BBQBufferQueueProducer; +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + friend class BBQBufferQueueCore; +#endif // can't be copied BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs); @@ -219,6 +223,10 @@ private: ui::Size mRequestedSize GUARDED_BY(mMutex); int32_t mFormat GUARDED_BY(mMutex); + // Keep a copy of the current picture profile handle, so it can be moved to a new + // SurfaceControl when BBQ migrates via ::update. + std::optional<PictureProfileHandle> mPictureProfileHandle; + struct BufferInfo { bool hasBuffer = false; uint32_t width; @@ -317,48 +325,47 @@ private: std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex); #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to the + // client. + std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mBufferReleaseConsumer; + std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer; + + void updateBufferReleaseProducer() REQUIRES(mMutex); + void drainBufferReleaseConsumer(); + + // BufferReleaseReader is used to do blocking but interruptible reads from the buffer + // release channel. To implement this, BufferReleaseReader owns an epoll file descriptor that + // is configured to wake up when either the BufferReleaseReader::ConsumerEndpoint or an eventfd + // becomes readable. Interrupts are necessary because a free buffer may become available for + // reasons other than a buffer release from the producer. class BufferReleaseReader { public: - BufferReleaseReader() = default; - BufferReleaseReader(std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint>); - BufferReleaseReader& operator=(BufferReleaseReader&&); + explicit BufferReleaseReader(BLASTBufferQueue&); + + BufferReleaseReader(const BufferReleaseReader&) = delete; + BufferReleaseReader& operator=(const BufferReleaseReader&) = delete; // Block until we can read a buffer release message. // // Returns: // * OK if a ReleaseCallbackId and Fence were successfully read. // * WOULD_BLOCK if the blocking read was interrupted by interruptBlockingRead. + // * TIMED_OUT if the blocking read timed out. // * UNKNOWN_ERROR if something went wrong. status_t readBlocking(ReleaseCallbackId& outId, sp<Fence>& outReleaseFence, - uint32_t& outMaxAcquiredBufferCount); + uint32_t& outMaxAcquiredBufferCount, nsecs_t timeout); - // Signals the reader's eventfd to wake up any threads waiting on readBlocking. void interruptBlockingRead(); + void clearInterrupts(); private: - std::mutex mMutex; - std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mEndpoint GUARDED_BY(mMutex); + BLASTBufferQueue& mBbq; + android::base::unique_fd mEpollFd; android::base::unique_fd mEventFd; }; - // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to - // the client. See BBQBufferQueueProducer::dequeueBuffer for details. - std::shared_ptr<BufferReleaseReader> mBufferReleaseReader; - std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer; - - class BufferReleaseThread { - public: - BufferReleaseThread() = default; - ~BufferReleaseThread(); - void start(const sp<BLASTBufferQueue>&); - - private: - std::shared_ptr<std::atomic_bool> mRunning; - std::shared_ptr<BufferReleaseReader> mReader; - }; - - BufferReleaseThread mBufferReleaseThread; + std::optional<BufferReleaseReader> mBufferReleaseReader; #endif }; diff --git a/libs/gui/include/gui/BufferItem.h b/libs/gui/include/gui/BufferItem.h index 218bb424fb..2f85c62a54 100644 --- a/libs/gui/include/gui/BufferItem.h +++ b/libs/gui/include/gui/BufferItem.h @@ -17,9 +17,12 @@ #ifndef ANDROID_GUI_BUFFERITEM_H #define ANDROID_GUI_BUFFERITEM_H +#include <optional> + #include <gui/HdrMetadata.h> #include <ui/FenceTime.h> +#include <ui/PictureProfileHandle.h> #include <ui/Rect.h> #include <ui/Region.h> @@ -91,6 +94,10 @@ class BufferItem : public Flattenable<BufferItem> { // mHdrMetadata is the HDR metadata associated with this buffer slot. HdrMetadata mHdrMetadata; + // mPictureProfileHandle is a handle that points to a set of parameters that configure picture + // processing hardware to enhance the quality of buffer contents. + std::optional<PictureProfileHandle> mPictureProfileHandle; + // mFrameNumber is the number of the queued frame for this slot. uint64_t mFrameNumber; diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h index d5dd7c897c..77cdf2c9f3 100644 --- a/libs/gui/include/gui/BufferQueueCore.h +++ b/libs/gui/include/gui/BufferQueueCore.h @@ -80,6 +80,13 @@ public: BufferQueueCore(); virtual ~BufferQueueCore(); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) +protected: + // Wake up any threads waiting for a buffer release. The BufferQueue mutex should always held + // when this method is called. + virtual void notifyBufferReleased() const; +#endif + private: // Dump our state in a string void dumpState(const String8& prefix, String8* outResult) const; diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h index 37a960708c..086ce7ce56 100644 --- a/libs/gui/include/gui/BufferQueueProducer.h +++ b/libs/gui/include/gui/BufferQueueProducer.h @@ -218,6 +218,14 @@ protected: // total maximum buffer count for the buffer queue (dequeued AND acquired) status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) + // Wait until a buffer has been released. The method may spuriously return OK when no buffer has + // been released. The BufferQueue mutex is passed in the locked state. It must be unlocked + // before waiting for a release and locked before returning. + virtual status_t waitForBufferRelease(std::unique_lock<std::mutex>& lock, + nsecs_t timeout) const; +#endif + private: // This is required by the IBinder::DeathRecipient interface virtual void binderDied(const wp<IBinder>& who); diff --git a/libs/gui/include/gui/BufferReleaseChannel.h b/libs/gui/include/gui/BufferReleaseChannel.h index 51fe0b6fab..0edadecedf 100644 --- a/libs/gui/include/gui/BufferReleaseChannel.h +++ b/libs/gui/include/gui/BufferReleaseChannel.h @@ -69,7 +69,8 @@ public: sp<Fence>& outReleaseFence, uint32_t& maxAcquiredBufferCount); private: - std::vector<uint8_t> mFlattenedBuffer; + std::mutex mMutex; + std::vector<uint8_t> mFlattenedBuffer GUARDED_BY(mMutex); }; class ProducerEndpoint : public Endpoint, public Parcelable { diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h index 5b32710135..e83d2e33f2 100644 --- a/libs/gui/include/gui/BufferSlot.h +++ b/libs/gui/include/gui/BufferSlot.h @@ -174,26 +174,30 @@ struct BufferState { }; struct BufferSlot { - BufferSlot() - : mGraphicBuffer(nullptr), - mEglDisplay(EGL_NO_DISPLAY), - mBufferState(), - mRequestBufferCalled(false), - mFrameNumber(0), - mEglFence(EGL_NO_SYNC_KHR), - mFence(Fence::NO_FENCE), - mAcquireCalled(false), - mNeedsReallocation(false) { + : mGraphicBuffer(nullptr), +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) + mEglDisplay(EGL_NO_DISPLAY), +#endif + mBufferState(), + mRequestBufferCalled(false), + mFrameNumber(0), +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) + mEglFence(EGL_NO_SYNC_KHR), +#endif + mFence(Fence::NO_FENCE), + mAcquireCalled(false), + mNeedsReallocation(false) { } // mGraphicBuffer points to the buffer allocated for this slot or is NULL // if no buffer has been allocated. sp<GraphicBuffer> mGraphicBuffer; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects. EGLDisplay mEglDisplay; - +#endif // mBufferState is the current state of this buffer slot. BufferState mBufferState; @@ -207,12 +211,14 @@ struct BufferSlot { // may be released before their release fence is signaled). uint64_t mFrameNumber; +#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP) // mEglFence is the EGL sync object that must signal before the buffer // associated with this buffer slot may be dequeued. It is initialized // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a // new sync object in releaseBuffer. (This is deprecated in favor of // mFence, below.) EGLSyncKHR mEglFence; +#endif // mFence is a fence which will signal when work initiated by the // previous owner of the buffer is finished. When the buffer is FREE, diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index 2e5aa4a893..a93ba14c57 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -127,6 +127,7 @@ private: std::vector<FrameRateOverride> overrides) override; void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel) override; + void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override; void scheduleCallbacks(); diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index 82cd50c7bd..b06ad077f4 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -68,6 +68,8 @@ private: virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel) = 0; + virtual void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) = 0; + bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount, VsyncEventData* outVsyncEventData); diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index 4dbf9e1929..ab6a6b78f7 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -62,6 +62,7 @@ public: DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'), DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'), DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'), + DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'), DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'), DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'), DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'), @@ -96,6 +97,10 @@ public: nsecs_t vsyncPeriod __attribute__((aligned(8))); }; + struct ModeRejection { + int32_t modeId; + }; + struct FrameRateOverride { uid_t uid __attribute__((aligned(8))); float frameRateHz __attribute__((aligned(8))); @@ -117,9 +122,10 @@ public: ModeChange modeChange; FrameRateOverride frameRateOverride; HdcpLevelsChange hdcpLevelsChange; + ModeRejection modeRejection; }; }; - static_assert(sizeof(Event) == 216); + static_assert(sizeof(Event) == 224); public: /* diff --git a/libs/gui/include/gui/DisplayLuts.h b/libs/gui/include/gui/DisplayLuts.h new file mode 100644 index 0000000000..ab86ac4af8 --- /dev/null +++ b/libs/gui/include/gui/DisplayLuts.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 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 <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <vector> + +namespace android::gui { + +struct DisplayLuts : public Parcelable { +public: + struct Entry : public Parcelable { + Entry() {}; + Entry(int32_t lutDimension, int32_t lutSize, int32_t lutSamplingKey) + : dimension(lutDimension), size(lutSize), samplingKey(lutSamplingKey) {} + int32_t dimension; + int32_t size; + int32_t samplingKey; + + status_t writeToParcel(android::Parcel* parcel) const override; + status_t readFromParcel(const android::Parcel* parcel) override; + }; + + DisplayLuts() {} + + DisplayLuts(base::unique_fd lutfd, std::vector<int32_t> lutoffsets, + std::vector<int32_t> lutdimensions, std::vector<int32_t> lutsizes, + std::vector<int32_t> lutsamplingKeys) { + fd = std::move(lutfd); + offsets = lutoffsets; + lutProperties.reserve(offsets.size()); + for (size_t i = 0; i < lutoffsets.size(); i++) { + Entry entry{lutdimensions[i], lutsizes[i], lutsamplingKeys[i]}; + lutProperties.emplace_back(entry); + } + } + + status_t writeToParcel(android::Parcel* parcel) const override; + status_t readFromParcel(const android::Parcel* parcel) override; + + const base::unique_fd& getLutFileDescriptor() const { return fd; } + + std::vector<Entry> lutProperties; + std::vector<int32_t> offsets; + +private: + base::unique_fd fd; +}; // struct DisplayLuts + +} // namespace android::gui
\ No newline at end of file diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h index 735375a1e7..845bc54c71 100644 --- a/libs/gui/include/gui/Flags.h +++ b/libs/gui/include/gui/Flags.h @@ -17,8 +17,40 @@ #pragma once #include <com_android_graphics_libgui_flags.h> +#include <utils/StrongPointer.h> + +namespace android { + +class IGraphicBufferProducer; +class Surface; +namespace view { +class Surface; +} #define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES \ (COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \ COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) && \ - COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
\ No newline at end of file + COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)) + +#define WB_LIBCAMERASERVICE_WITH_DEPENDENCIES \ + (WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES && \ + COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_LIBCAMERASERVICE)) + +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES +typedef android::Surface SurfaceType; +typedef android::view::Surface ParcelableSurfaceType; +#else +typedef android::IGraphicBufferProducer SurfaceType; +typedef android::sp<android::IGraphicBufferProducer> ParcelableSurfaceType; +#endif + +namespace flagtools { +sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface); +ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface); +sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface); +bool isSurfaceTypeValid(const sp<SurfaceType>& surface); +ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface); +sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface); +} // namespace flagtools + +} // namespace android diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h index bfe3eb31e8..8a66dc0cf1 100644 --- a/libs/gui/include/gui/GLConsumer.h +++ b/libs/gui/include/gui/GLConsumer.h @@ -268,9 +268,9 @@ protected: // releaseBufferLocked overrides the ConsumerBase method to update the // mEglSlots array in addition to the ConsumerBase. - virtual status_t releaseBufferLocked(int slot, - const sp<GraphicBuffer> graphicBuffer, - EGLDisplay display, EGLSyncKHR eglFence) override; + virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer, + EGLDisplay display = EGL_NO_DISPLAY, + EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override; status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer, EGLSyncKHR eglFence) { diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h index 0b92e7df62..18f5488173 100644 --- a/libs/gui/include/gui/IGraphicBufferConsumer.h +++ b/libs/gui/include/gui/IGraphicBufferConsumer.h @@ -137,16 +137,9 @@ public: virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display, EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0; - status_t releaseHelper(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) { + status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) { return releaseBuffer(buf, frameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence); } - // This is explicitly *not* the actual signature of IGBC::releaseBuffer, but: - // 1) We have no easy way to send the EGL objects across Binder - // 2) This has always been broken, probably because - // 3) IGBC is rarely remoted - // For now, we will choose to bury our heads in the sand and ignore this problem until such time - // as we can finally finish converting away from EGL sync to native Android sync - using ReleaseBuffer = decltype(&IGraphicBufferConsumer::releaseHelper); // consumerConnect connects a consumer to the BufferQueue. Only one consumer may be connected, // and when that consumer disconnects the BufferQueue is placed into the "abandoned" state, diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h index 197e792367..a42ddc466c 100644 --- a/libs/gui/include/gui/IGraphicBufferProducer.h +++ b/libs/gui/include/gui/IGraphicBufferProducer.h @@ -19,6 +19,7 @@ #include <stdint.h> #include <sys/types.h> +#include <optional> #include <utils/Errors.h> #include <utils/RefBase.h> @@ -28,6 +29,7 @@ #include <ui/BufferQueueDefs.h> #include <ui/Fence.h> #include <ui/GraphicBuffer.h> +#include <ui/PictureProfileHandle.h> #include <ui/Rect.h> #include <ui/Region.h> @@ -365,6 +367,14 @@ public: const HdrMetadata& getHdrMetadata() const { return hdrMetadata; } void setHdrMetadata(const HdrMetadata& metadata) { hdrMetadata = metadata; } + const std::optional<PictureProfileHandle>& getPictureProfileHandle() const { + return pictureProfileHandle; + } + void setPictureProfileHandle(const PictureProfileHandle& profile) { + pictureProfileHandle = profile; + } + void clearPictureProfileHandle() { pictureProfileHandle = std::nullopt; } + int64_t timestamp{0}; int isAutoTimestamp{0}; android_dataspace dataSpace{HAL_DATASPACE_UNKNOWN}; @@ -377,6 +387,7 @@ public: bool getFrameTimestamps{false}; int slot{-1}; HdrMetadata hdrMetadata; + std::optional<PictureProfileHandle> pictureProfileHandle; }; struct QueueBufferOutput : public Flattenable<QueueBufferOutput> { diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 2cdde3255e..1c31e46cb4 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -26,6 +26,7 @@ #include <android/gui/LayerCaptureArgs.h> #include <android/gui/TrustedPresentationThresholds.h> #include <android/native_window.h> +#include <gui/DisplayLuts.h> #include <gui/IGraphicBufferProducer.h> #include <gui/ITransactionCompletedListener.h> #include <math/mat4.h> @@ -46,6 +47,7 @@ #include <ui/BlurRegion.h> #include <ui/GraphicTypes.h> #include <ui/LayerStack.h> +#include <ui/PictureProfileHandle.h> #include <ui/Rect.h> #include <ui/Region.h> #include <ui/Rotation.h> @@ -169,6 +171,10 @@ struct layer_state_t { // Sets a property on this layer indicating that its visible region should be considered // when computing TrustedPresentation Thresholds. eCanOccludePresentation = 0x1000, + // Indicates that the SurfaceControl should recover from buffer stuffing when + // possible. This is the case when the SurfaceControl is the root SurfaceControl + // owned by ViewRootImpl. + eRecoverableFromBufferStuffing = 0x2000, }; enum { @@ -184,6 +190,7 @@ struct layer_state_t { eCachingHintChanged = 0x00000200, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, + eLutsChanged = 0x00001000, eBufferCropChanged = 0x00002000, eRelativeLayerChanged = 0x00004000, eReparent = 0x00008000, @@ -222,6 +229,8 @@ struct layer_state_t { eExtendedRangeBrightnessChanged = 0x10000'00000000, eEdgeExtensionChanged = 0x20000'00000000, eBufferReleaseChannelChanged = 0x40000'00000000, + ePictureProfileHandleChanged = 0x80000'00000000, + eAppContentPriorityChanged = 0x100000'00000000, }; layer_state_t(); @@ -255,7 +264,7 @@ struct layer_state_t { layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eTransparentRegionChanged | layer_state_t::eExtendedRangeBrightnessChanged | - layer_state_t::eDesiredHdrHeadroomChanged; + layer_state_t::eDesiredHdrHeadroomChanged | layer_state_t::eLutsChanged; // Content updates. static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES | @@ -265,7 +274,8 @@ struct layer_state_t { layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged | layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged | - layer_state_t::eStretchChanged; + layer_state_t::eStretchChanged | layer_state_t::ePictureProfileHandleChanged | + layer_state_t::eAppContentPriorityChanged; // Changes which invalidates the layer's visible region in CE. static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES | @@ -330,7 +340,7 @@ struct layer_state_t { Region transparentRegion; uint32_t bufferTransform; bool transformToDisplayInverse; - Rect crop; + FloatRect crop; std::shared_ptr<BufferData> bufferData = nullptr; ui::Dataspace dataspace; HdrMetadata hdrMetadata; @@ -410,12 +420,23 @@ struct layer_state_t { float currentHdrSdrRatio = 1.f; float desiredHdrSdrRatio = 1.f; + // Enhance the quality of the buffer contents by configurating a picture processing pipeline + // with values as specified by this picture profile. + PictureProfileHandle pictureProfileHandle{PictureProfileHandle::NONE}; + + // A value indicating the significance of the layer's content to the app's desired user + // experience. A lower priority will result in more likelihood of getting access to limited + // resources, such as picture processing hardware. + int32_t appContentPriority = 0; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; TrustedPresentationThresholds trustedPresentationThresholds; TrustedPresentationListener trustedPresentationListener; std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel; + + std::shared_ptr<gui::DisplayLuts> luts; }; class ComposerState { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 4f9af16826..0ce0c0a9c3 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -38,6 +38,7 @@ #include <ui/EdgeExtensionEffect.h> #include <ui/FrameStats.h> #include <ui/GraphicTypes.h> +#include <ui/PictureProfileHandle.h> #include <ui/PixelFormat.h> #include <ui/Rotation.h> #include <ui/StaticDisplayInfo.h> @@ -297,6 +298,8 @@ public: static status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& listener); + static status_t setActivePictureListener(const sp<gui::IActivePictureListener>& listener); + /* * Sends a power boost to the composer. This function is asynchronous. * @@ -342,6 +345,15 @@ public: static bool flagEdgeExtensionEffectUseShader(); + /** + * Returns how many picture profiles are supported by the display. + * + * displayToken + * The token of the display. + */ + static status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken, + int32_t* outMaxProfiles); + // ------------------------------------------------------------------------ // surface creation / destruction @@ -437,6 +449,8 @@ public: static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other); // Tracks registered callbacks sp<TransactionCompletedListener> mTransactionCompletedListener = nullptr; + // Prints debug logs when enabled. + bool mLogCallPoints = false; protected: std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates; @@ -549,6 +563,7 @@ public: Transaction& setMatrix(const sp<SurfaceControl>& sc, float dsdx, float dtdx, float dtdy, float dsdy); Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop); + Transaction& setCrop(const sp<SurfaceControl>& sc, const FloatRect& crop); Transaction& setCornerRadius(const sp<SurfaceControl>& sc, float cornerRadius); Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc, int backgroundBlurRadius); @@ -601,6 +616,11 @@ public: Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc, float currentBufferRatio, float desiredRatio); Transaction& setDesiredHdrHeadroom(const sp<SurfaceControl>& sc, float desiredRatio); + Transaction& setLuts(const sp<SurfaceControl>& sc, const base::unique_fd& lutFd, + const std::vector<int32_t>& offsets, + const std::vector<int32_t>& dimensions, + const std::vector<int32_t>& sizes, + const std::vector<int32_t>& samplingKeys); Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint); Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata); Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc, @@ -679,7 +699,8 @@ public: // ONLY FOR BLAST ADAPTER Transaction& notifyProducerDisconnect(const sp<SurfaceControl>& sc); - Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc, const gui::WindowInfo& info); + Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc, + sp<gui::WindowInfoHandle> info); Transaction& setFocusedWindow(const gui::FocusRequest& request); Transaction& addWindowInfosReportedListener( @@ -767,6 +788,20 @@ public: const sp<SurfaceControl>& sc, const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel); + /** + * Configures a surface control to use picture processing hardware, configured as specified + * by the picture profile, to enhance the quality of all subsequent buffer contents. + */ + Transaction& setPictureProfileHandle(const sp<SurfaceControl>& sc, + const PictureProfileHandle& pictureProfileHandle); + + /** + * Configures the relative importance of the contents of the layer with respect to the app's + * user experience. A lower priority value will give the layer preferred access to limited + * resources, such as picture processing, over a layer with a higher priority value. + */ + Transaction& setContentPriority(const sp<SurfaceControl>& sc, int32_t contentPriority); + status_t setDisplaySurface(const sp<IBinder>& token, const sp<IGraphicBufferProducer>& bufferProducer); @@ -803,6 +838,7 @@ public: static void setDefaultApplyToken(sp<IBinder> applyToken); static status_t sendSurfaceFlushJankDataTransaction(const sp<SurfaceControl>& sc); + void enableDebugLogCallPoints(); }; status_t clearLayerFrameStats(const sp<IBinder>& token) const; diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h index b40a84099c..ced5130505 100644 --- a/libs/gui/include/gui/VsyncEventData.h +++ b/libs/gui/include/gui/VsyncEventData.h @@ -36,6 +36,9 @@ struct VsyncEventData { // Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity. uint32_t frameTimelinesLength; + // Number of queued buffers to indicate if buffer stuffing mode is detected. + uint32_t numberQueuedBuffers; + struct alignas(8) FrameTimeline { // The Vsync Id corresponsing to this vsync event. This will be used to // populate ISurfaceComposer::setFrameTimelineVsync and diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h index 7ddac8139a..bd8704ddc7 100644 --- a/libs/gui/include/gui/view/Surface.h +++ b/libs/gui/include/gui/view/Surface.h @@ -24,7 +24,9 @@ #include <binder/IBinder.h> #include <binder/Parcelable.h> +#include <gui/Flags.h> #include <gui/IGraphicBufferProducer.h> +#include <gui/Surface.h> namespace android { @@ -46,6 +48,30 @@ class Surface : public Parcelable { sp<IGraphicBufferProducer> graphicBufferProducer; sp<IBinder> surfaceControlHandle; +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES + // functions used to convert to a parcelable Surface so it can be passed over binder. + static Surface fromSurface(const sp<android::Surface>& surface); + sp<android::Surface> toSurface() const; + + status_t getUniqueId(/* out */ uint64_t* id) const; + + bool isEmpty() const; + + bool operator==(const Surface& other) const { + return graphicBufferProducer == other.graphicBufferProducer; + } + bool operator!=(const Surface& other) const { return !(*this == other); } + bool operator==(const sp<android::Surface> other) const { + if (other == nullptr) return graphicBufferProducer == nullptr; + return graphicBufferProducer == other->getIGraphicBufferProducer(); + } + bool operator!=(const sp<android::Surface> other) const { return !(*this == other); } + bool operator<(const Surface& other) const { + return graphicBufferProducer < other.graphicBufferProducer; + } + bool operator>(const Surface& other) const { return other < *this; } +#endif + virtual status_t writeToParcel(Parcel* parcel) const override; virtual status_t readFromParcel(const Parcel* parcel) override; diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig index d3f2899ba3..6bf38c05f1 100644 --- a/libs/gui/libgui_flags.aconfig +++ b/libs/gui/libgui_flags.aconfig @@ -2,6 +2,14 @@ package: "com.android.graphics.libgui.flags" container: "system" flag { + name: "apply_picture_profiles" + namespace: "tv_os_media" + description: "This flag controls sending picture profiles from BBQ to Composer HAL" + bug: "337330263" + is_fixed_read_only: true +} # apply_picture_profiles + +flag { name: "bq_setframerate" namespace: "core_graphics" description: "This flag controls plumbing setFrameRate thru BufferQueue" @@ -109,9 +117,25 @@ flag { } # wb_libcameraservice flag { + name: "wb_unlimited_slots" + namespace: "core_graphics" + description: "Adds APIs and updates the implementation of bufferqueues to have a user-defined slot count." + bug: "341359814" + is_fixed_read_only: true +} # wb_unlimited_slots + +flag { name: "bq_producer_throttles_only_async_mode" namespace: "core_graphics" description: "BufferQueueProducer only CPU throttle on queueBuffer() in async mode." bug: "359252619" is_fixed_read_only: true } # bq_producer_throttles_only_async_mode + +flag { + name: "bq_gl_fence_cleanup" + namespace: "core_graphics" + description: "Remove BufferQueueProducer::dequeue's wait on this fence (or the fence entirely) to prevent deadlocks" + bug: "339705065" + is_fixed_read_only: true +} # bq_gl_fence_cleanup diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index 2e6ffcb57f..16060990bd 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -27,6 +27,7 @@ #include <gui/Surface.h> #include <ui/GraphicBuffer.h> +#include <ui/PictureProfileHandle.h> #include <android-base/properties.h> @@ -424,8 +425,7 @@ TEST_F(BufferQueueTest, DetachAndReattachOnConsumerSide) { ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, nullptr)); ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, Fence::NO_FENCE)); ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, GRALLOC_USAGE_SW_WRITE_OFTEN, @@ -608,8 +608,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithoutAutoRefresh) { ASSERT_EQ(true, item.mQueuedBuffer); ASSERT_EQ(false, item.mAutoRefresh); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // attempt to acquire a second time should return no buffer available ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE, @@ -652,8 +651,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) { ASSERT_EQ(i == 0, item.mQueuedBuffer); ASSERT_EQ(true, item.mAutoRefresh); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); } // Repeatedly queue and dequeue a buffer from the producer side, it should @@ -683,8 +681,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) { ASSERT_EQ(i == 0, item.mQueuedBuffer); ASSERT_EQ(true, item.mAutoRefresh); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); } } @@ -734,8 +731,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeUsingAlreadyDequeuedBuffer) { ASSERT_EQ(true, item.mQueuedBuffer); ASSERT_EQ(false, item.mAutoRefresh); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // attempt to acquire a second time should return no buffer available ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE, @@ -873,8 +869,7 @@ TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) { for (size_t i = 0; i < 2; ++i) { BufferItem item; ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); } // Make sure we got the second buffer back @@ -928,8 +923,7 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) { nullptr, nullptr)); ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); std::this_thread::sleep_for(16ms); } @@ -945,8 +939,7 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) { nullptr, nullptr)); ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); std::this_thread::sleep_for(16ms); } ASSERT_EQ(OK, @@ -958,12 +951,10 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) { nullptr)); ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); std::this_thread::sleep_for(16ms); ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // Sleep between segments std::this_thread::sleep_for(500ms); @@ -980,13 +971,11 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) { nullptr, nullptr)); ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); std::this_thread::sleep_for(16ms); } ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // Now we read the segments std::vector<OccupancyTracker::Segment> history; @@ -1107,8 +1096,7 @@ TEST_F(BufferQueueTest, TestDiscardFreeBuffers) { // Acquire and free 1 buffer ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); int releasedSlot = item.mSlot; // Acquire 1 buffer, leaving 1 filled buffer in queue @@ -1375,8 +1363,7 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) { ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); ASSERT_EQ(slot, item.mSlot); ASSERT_NE(nullptr, item.mGraphicBuffer.get()); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // Dequeue and queue the buffer again ASSERT_EQ(OK, @@ -1389,8 +1376,7 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) { ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); ASSERT_EQ(slot, item.mSlot); ASSERT_EQ(nullptr, item.mGraphicBuffer.get()); - ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // Dequeue and queue the buffer again ASSERT_EQ(OK, @@ -1569,4 +1555,61 @@ TEST_F(BufferQueueTest, TestAdditionalOptions) { EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace); } +TEST_F(BufferQueueTest, PassesThroughPictureProfileHandle) { + createBufferQueue(); + sp<MockConsumer> mc(new MockConsumer); + mConsumer->consumerConnect(mc, false); + + IGraphicBufferProducer::QueueBufferOutput qbo; + mProducer->connect(new StubProducerListener, NATIVE_WINDOW_API_CPU, false, &qbo); + mProducer->setMaxDequeuedBufferCount(2); + mConsumer->setMaxAcquiredBufferCount(2); + + // First try to pass a valid picture profile handle + { + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buf; + IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN, + Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, + Fence::NO_FENCE); + qbi.setPictureProfileHandle(PictureProfileHandle(1)); + + EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN, + nullptr, nullptr)); + EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf)); + EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo)); + + BufferItem item; + EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + + ASSERT_TRUE(item.mPictureProfileHandle.has_value()); + ASSERT_EQ(item.mPictureProfileHandle, PictureProfileHandle(1)); + } + + // Then validate that the picture profile handle isn't sticky and is reset for the next buffer + { + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buf; + IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN, + Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, + Fence::NO_FENCE); + + EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN, + nullptr, nullptr)); + EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf)); + EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo)); + + BufferItem item; + EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + + ASSERT_FALSE(item.mPictureProfileHandle.has_value()); + } +} + } // namespace android diff --git a/libs/gui/tests/BufferReleaseChannel_test.cpp b/libs/gui/tests/BufferReleaseChannel_test.cpp index 11d122b525..74f69e1ff0 100644 --- a/libs/gui/tests/BufferReleaseChannel_test.cpp +++ b/libs/gui/tests/BufferReleaseChannel_test.cpp @@ -29,11 +29,11 @@ namespace { // Helper function to check if two file descriptors point to the same file. bool is_same_file(int fd1, int fd2) { - struct stat stat1; + struct stat stat1 {}; if (fstat(fd1, &stat1) != 0) { return false; } - struct stat stat2; + struct stat stat2 {}; if (fstat(fd2, &stat2) != 0) { return false; } @@ -42,7 +42,18 @@ bool is_same_file(int fd1, int fd2) { } // namespace -TEST(BufferReleaseChannelTest, MessageFlattenable) { +class BufferReleaseChannelTest : public testing::Test { +protected: + std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> mConsumer; + std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> mProducer; + + void SetUp() override { + ASSERT_EQ(OK, + BufferReleaseChannel::open("BufferReleaseChannelTest"s, mConsumer, mProducer)); + } +}; + +TEST_F(BufferReleaseChannelTest, MessageFlattenable) { ReleaseCallbackId releaseCallbackId{1, 2}; sp<Fence> releaseFence = sp<Fence>::make(memfd_create("fake-fence-fd", 0)); uint32_t maxAcquiredBufferCount = 5; @@ -92,31 +103,23 @@ TEST(BufferReleaseChannelTest, MessageFlattenable) { // Verify that the BufferReleaseChannel consume returns WOULD_BLOCK when there's no message // available. -TEST(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) { - std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer; - std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer; - ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer)); - +TEST_F(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) { ReleaseCallbackId releaseCallbackId; sp<Fence> releaseFence; uint32_t maxAcquiredBufferCount; ASSERT_EQ(WOULD_BLOCK, - consumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount)); + mConsumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount)); } // Verify that we can write a message to the BufferReleaseChannel producer and read that message // using the BufferReleaseChannel consumer. -TEST(BufferReleaseChannelTest, ProduceAndConsume) { - std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer; - std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer; - ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer)); - +TEST_F(BufferReleaseChannelTest, ProduceAndConsume) { sp<Fence> fence = sp<Fence>::make(memfd_create("fake-fence-fd", 0)); for (uint64_t i = 0; i < 64; i++) { ReleaseCallbackId producerId{i, i + 1}; uint32_t maxAcquiredBufferCount = i + 2; - ASSERT_EQ(OK, producer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount)); + ASSERT_EQ(OK, mProducer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount)); } for (uint64_t i = 0; i < 64; i++) { @@ -127,7 +130,7 @@ TEST(BufferReleaseChannelTest, ProduceAndConsume) { sp<Fence> consumerFence; uint32_t maxAcquiredBufferCount; ASSERT_EQ(OK, - consumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount)); + mConsumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount)); ASSERT_EQ(expectedId, consumerId); ASSERT_TRUE(is_same_file(fence->get(), consumerFence->get())); @@ -135,4 +138,16 @@ TEST(BufferReleaseChannelTest, ProduceAndConsume) { } } +// Verify that BufferReleaseChannel::ConsumerEndpoint's socket can't be written to. +TEST_F(BufferReleaseChannelTest, ConsumerSocketReadOnly) { + uint64_t data = 0; + ASSERT_EQ(-1, write(mConsumer->getFd().get(), &data, sizeof(uint64_t))); + ASSERT_EQ(errno, EPIPE); +} + +// Verify that BufferReleaseChannel::ProducerEndpoint's socket can't be read from. +TEST_F(BufferReleaseChannelTest, ProducerSocketWriteOnly) { + ASSERT_EQ(0, read(mProducer->getFd().get(), nullptr, sizeof(uint64_t))); +} + } // namespace android
\ No newline at end of file diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp index 29eeaa806f..791f471a1d 100644 --- a/libs/gui/tests/DisplayEventStructLayout_test.cpp +++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp @@ -36,15 +36,16 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelinesLength, 20); - CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24); - CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24); + CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.numberQueuedBuffers, 24); + CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 32); + CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 32); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp, - 32); + 40); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[0].expectedPresentationTime, 40); + vsyncData.frameTimelines[0].expectedPresentationTime, 48); // Also test the offsets of the last frame timeline. A loop is not used because the non-const // index cannot be used in static_assert. - const int lastFrameTimelineOffset = /* Start of array */ 24 + + const int lastFrameTimelineOffset = /* Start of array */ 32 + (VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24; CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId, diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 7d0b512cb4..06f00a4d74 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -34,6 +34,8 @@ #include <binder/Parcel.h> #include <binder/ProcessState.h> +#include <gui/IConsumerListener.h> +#include <gui/IGraphicBufferConsumer.h> #include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> @@ -75,7 +77,7 @@ sp<IInputFlinger> getInputFlinger() { if (input == nullptr) { ALOGE("Failed to link to input service"); } else { - ALOGE("Linked to input"); + ALOGI("Linked to input"); } return interface_cast<IInputFlinger>(input); } @@ -112,91 +114,81 @@ public: mInputFlinger = getInputFlinger(); if (noInputChannel) { - mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true); + mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true); } else { android::os::InputChannelCore tempChannel; android::binder::Status result = - mInputFlinger->createInputChannel("testchannels", &tempChannel); + mInputFlinger->createInputChannel(sc->getName() + " channel", &tempChannel); if (!result.isOk()) { ADD_FAILURE() << "binder call to createInputChannel failed"; } mClientChannel = InputChannel::create(std::move(tempChannel)); - mInputInfo.token = mClientChannel->getConnectionToken(); + mInputInfo->editInfo()->token = mClientChannel->getConnectionToken(); mInputConsumer = new InputConsumer(mClientChannel); } - mInputInfo.name = "Test info"; - mInputInfo.dispatchingTimeout = 5s; - mInputInfo.globalScaleFactor = 1.0; - mInputInfo.touchableRegion.orSelf(Rect(0, 0, width, height)); + mInputInfo->editInfo()->name = "Test info"; + mInputInfo->editInfo()->dispatchingTimeout = 5s; + mInputInfo->editInfo()->globalScaleFactor = 1.0; + mInputInfo->editInfo()->touchableRegion.orSelf(Rect(0, 0, width, height)); InputApplicationInfo aInfo; aInfo.token = new BBinder(); aInfo.name = "Test app info"; aInfo.dispatchingTimeoutMillis = std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count(); - mInputInfo.applicationInfo = aInfo; + mInputInfo->editInfo()->applicationInfo = aInfo; } static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient>& scc, - int width, int height) { + int width, int height, + const std::string& name) { sp<SurfaceControl> surfaceControl = - scc->createSurface(String8("Test Surface"), 0 /* bufHeight */, 0 /* bufWidth */, + scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceEffect); return std::make_unique<InputSurface>(surfaceControl, width, height); } static std::unique_ptr<InputSurface> makeBufferInputSurface( - const sp<SurfaceComposerClient>& scc, int width, int height) { + const sp<SurfaceComposerClient>& scc, int width, int height, + const std::string& name = "Test Buffer Surface") { sp<SurfaceControl> surfaceControl = - scc->createSurface(String8("Test Buffer Surface"), width, height, - PIXEL_FORMAT_RGBA_8888, 0 /* flags */); + scc->createSurface(String8(name.c_str()), width, height, PIXEL_FORMAT_RGBA_8888, + 0 /* flags */); return std::make_unique<InputSurface>(surfaceControl, width, height); } static std::unique_ptr<InputSurface> makeContainerInputSurface( - const sp<SurfaceComposerClient>& scc, int width, int height) { + const sp<SurfaceComposerClient>& scc, int width, int height, + const std::string& name = "Test Container Surface") { sp<SurfaceControl> surfaceControl = - scc->createSurface(String8("Test Container Surface"), 0 /* bufHeight */, - 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, + scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */, + PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer); return std::make_unique<InputSurface>(surfaceControl, width, height); } static std::unique_ptr<InputSurface> makeContainerInputSurfaceNoInputChannel( - const sp<SurfaceComposerClient>& scc, int width, int height) { + const sp<SurfaceComposerClient>& scc, int width, int height, + const std::string& name = "Test Container Surface") { sp<SurfaceControl> surfaceControl = - scc->createSurface(String8("Test Container Surface"), 100 /* height */, - 100 /* width */, PIXEL_FORMAT_RGBA_8888, + scc->createSurface(String8(name.c_str()), 100 /* height */, 100 /* width */, + PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer); return std::make_unique<InputSurface>(surfaceControl, width, height, true /* noInputChannel */); } static std::unique_ptr<InputSurface> makeCursorInputSurface( - const sp<SurfaceComposerClient>& scc, int width, int height) { + const sp<SurfaceComposerClient>& scc, int width, int height, + const std::string& name = "Test Cursor Surface") { sp<SurfaceControl> surfaceControl = - scc->createSurface(String8("Test Cursor Surface"), 0 /* bufHeight */, - 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eCursorWindow); + scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */, + PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eCursorWindow); return std::make_unique<InputSurface>(surfaceControl, width, height); } - InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) { - mClientChannel->waitForMessage(timeout); - - InputEvent* ev; - uint32_t seqId; - status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev); - if (consumed != OK) { - return nullptr; - } - status_t status = mInputConsumer->sendFinishedSignal(seqId, true); - EXPECT_EQ(OK, status) << "Could not send finished signal"; - return ev; - } - void assertFocusChange(bool hasFocus) { InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); @@ -294,7 +286,7 @@ public: transactionBody) { SurfaceComposerClient::Transaction t; transactionBody(t, mSurfaceControl); - t.apply(true); + t.apply(/*synchronously=*/true); } virtual void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) { @@ -307,30 +299,46 @@ public: t.setAlpha(mSurfaceControl, 1); auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make(); t.addWindowInfosReportedListener(reportedListener); - t.apply(); + t.apply(/*synchronously=*/true); reportedListener->wait(); } void requestFocus(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT) { SurfaceComposerClient::Transaction t; FocusRequest request; - request.token = mInputInfo.token; - request.windowName = mInputInfo.name; + request.token = mInputInfo->getInfo()->token; + request.windowName = mInputInfo->getInfo()->name; request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = displayId.val(); t.setFocusedWindow(request); - t.apply(true); + t.apply(/*synchronously=*/true); } public: + // But should be private + sp<gui::WindowInfoHandle> mInputInfo = sp<gui::WindowInfoHandle>::make(); sp<SurfaceControl> mSurfaceControl; + +private: std::shared_ptr<InputChannel> mClientChannel; sp<IInputFlinger> mInputFlinger; - WindowInfo mInputInfo; - PreallocatedInputEventFactory mInputEventFactory; InputConsumer* mInputConsumer; + + InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) { + mClientChannel->waitForMessage(timeout); + + InputEvent* ev; + uint32_t seqId; + status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev); + if (consumed != OK) { + return nullptr; + } + status_t status = mInputConsumer->sendFinishedSignal(seqId, true); + EXPECT_EQ(OK, status) << "Could not send finished signal"; + return ev; + } }; class BlastInputSurface : public InputSurface { @@ -363,7 +371,7 @@ public: transactionBody) override { SurfaceComposerClient::Transaction t; transactionBody(t, mParentSurfaceControl); - t.apply(true); + t.apply(/*synchronously=*/true); } void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) override { @@ -377,7 +385,7 @@ public: t.setInputWindowInfo(mSurfaceControl, mInputInfo); t.setCrop(mSurfaceControl, crop); t.setAlpha(mSurfaceControl, 1); - t.apply(true); + t.apply(/*synchronously=*/true); } private: @@ -408,8 +416,9 @@ public: void TearDown() { mComposerClient->dispose(); } - std::unique_ptr<InputSurface> makeSurface(int width, int height) { - return InputSurface::makeColorInputSurface(mComposerClient, width, height); + std::unique_ptr<InputSurface> makeSurface(int width, int height, + const std::string& name = "Test Surface") const { + return InputSurface::makeColorInputSurface(mComposerClient, width, height, name); } void postBuffer(const sp<SurfaceControl>& layer, int32_t w, int32_t h) { @@ -417,7 +426,7 @@ public: BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE; sp<GraphicBuffer> buffer = new GraphicBuffer(w, h, PIXEL_FORMAT_RGBA_8888, 1, usageFlags, "test"); - Transaction().setBuffer(layer, buffer).apply(true); + Transaction().setBuffer(layer, buffer).apply(/*synchronously=*/true); usleep(mBufferPostDelay); } @@ -458,7 +467,7 @@ TEST_F(InputSurfacesTest, can_receive_input) { injectTap(101, 101); - EXPECT_NE(surface->consumeEvent(), nullptr); + surface->expectTap(1, 1); } /** @@ -468,10 +477,10 @@ TEST_F(InputSurfacesTest, can_receive_input) { * reverse order. */ TEST_F(InputSurfacesTest, input_respects_positioning) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Left Surface"); surface->showAt(100, 100); - std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100, "Right Surface"); surface2->showAt(200, 200); injectTap(201, 201); @@ -491,8 +500,8 @@ TEST_F(InputSurfacesTest, input_respects_positioning) { } TEST_F(InputSurfacesTest, input_respects_layering) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); - std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface 1"); + std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100, "Test Surface 2"); surface->showAt(10, 10); surface2->showAt(10, 10); @@ -517,11 +526,11 @@ TEST_F(InputSurfacesTest, input_respects_layering) { // (such as shadows in dialogs). Inputs sent to the client are offset such that 0,0 is the start // of the client content. TEST_F(InputSurfacesTest, input_respects_surface_insets) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(100, 100); - fgSurface->mInputInfo.surfaceInset = 5; + fgSurface->mInputInfo->editInfo()->surfaceInset = 5; fgSurface->showAt(100, 100); injectTap(106, 106); @@ -532,12 +541,12 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets) { } TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(100, 100); - fgSurface->mInputInfo.surfaceInset = 5; - fgSurface->mInputInfo.replaceTouchableRegionWithCrop = true; + fgSurface->mInputInfo->editInfo()->surfaceInset = 5; + fgSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true; fgSurface->showAt(100, 100); injectTap(106, 106); @@ -549,11 +558,11 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableReg // Ensure a surface whose insets are cropped, handles the touch offset correctly. ref:b/120413463 TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) { - std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> childSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface"); + std::unique_ptr<InputSurface> childSurface = makeSurface(100, 100, "Child Surface"); parentSurface->showAt(100, 100); - childSurface->mInputInfo.surfaceInset = 10; + childSurface->mInputInfo->editInfo()->surfaceInset = 10; childSurface->showAt(100, 100); childSurface->doTransaction([&](auto& t, auto& sc) { @@ -570,11 +579,11 @@ TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) { // Ensure a surface whose insets are scaled, handles the touch offset correctly. TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(100, 100); - fgSurface->mInputInfo.surfaceInset = 5; + fgSurface->mInputInfo->editInfo()->surfaceInset = 5; fgSurface->showAt(100, 100); fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); }); @@ -588,12 +597,12 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets) { } TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(100, 100); // In case we pass the very big inset without any checking. - fgSurface->mInputInfo.surfaceInset = INT32_MAX; + fgSurface->mInputInfo->editInfo()->surfaceInset = INT32_MAX; fgSurface->showAt(100, 100); fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); @@ -606,28 +615,29 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) { TEST_F(InputSurfacesTest, touchable_region) { std::unique_ptr<InputSurface> surface = makeSurface(100, 100); - surface->mInputInfo.touchableRegion.set(Rect{19, 29, 21, 31}); + surface->mInputInfo->editInfo()->touchableRegion.set(Rect{19, 29, 21, 31}); surface->showAt(11, 22); // A tap within the surface but outside the touchable region should not be sent to the surface. injectTap(20, 30); - EXPECT_EQ(surface->consumeEvent(/*timeout=*/200ms), nullptr); + surface->assertNoEvent(); injectTap(31, 52); surface->expectTap(20, 30); } TEST_F(InputSurfacesTest, input_respects_touchable_region_offset_overflow) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(100, 100); // Set the touchable region to the values at the limit of its corresponding type. // Since the surface is offset from the origin, the touchable region will be transformed into // display space, which would trigger an overflow or an underflow. Ensure that we are protected // against such a situation. - fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}); + fgSurface->mInputInfo->editInfo()->touchableRegion.orSelf( + Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}); fgSurface->showAt(100, 100); @@ -638,11 +648,12 @@ TEST_F(InputSurfacesTest, input_respects_touchable_region_offset_overflow) { } TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(0, 0); - fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}); + fgSurface->mInputInfo->editInfo()->touchableRegion.orSelf( + Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}); fgSurface->showAt(0, 0); fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); @@ -703,8 +714,8 @@ TEST_F(InputSurfacesTest, input_respects_buffer_layer_alpha) { } TEST_F(InputSurfacesTest, input_ignores_color_layer_alpha) { - std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100); + std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface"); + std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface"); bgSurface->showAt(10, 10); fgSurface->showAt(10, 10); @@ -812,7 +823,7 @@ TEST_F(InputSurfacesTest, rotate_surface_with_scale) { TEST_F(InputSurfacesTest, rotate_surface_with_scale_and_insets) { std::unique_ptr<InputSurface> surface = makeSurface(100, 100); - surface->mInputInfo.surfaceInset = 5; + surface->mInputInfo->editInfo()->surfaceInset = 5; surface->showAt(100, 100); surface->doTransaction([](auto& t, auto& sc) { @@ -835,17 +846,19 @@ TEST_F(InputSurfacesTest, rotate_surface_with_scale_and_insets) { } TEST_F(InputSurfacesTest, touch_flag_obscured) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Touchable Surface"); surface->showAt(100, 100); // Add non touchable window to fully cover touchable window. Window behind gets touch, but // with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED - std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100); - nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222}; + std::unique_ptr<InputSurface> nonTouchableSurface = + makeSurface(100, 100, "Non-Touchable Surface"); + nonTouchableSurface->mInputInfo->editInfo() + ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); + nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; // Overriding occlusion mode otherwise the touch would be discarded at InputDispatcher by // the default obscured/untrusted touch filter introduced in S. - nonTouchableSurface->mInputInfo.touchOcclusionMode = TouchOcclusionMode::ALLOW; + nonTouchableSurface->mInputInfo->editInfo()->touchOcclusionMode = TouchOcclusionMode::ALLOW; nonTouchableSurface->showAt(100, 100); injectTap(190, 199); @@ -853,18 +866,21 @@ TEST_F(InputSurfacesTest, touch_flag_obscured) { } TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); surface->showAt(100, 100); // Add non touchable window to cover touchable window, but parent is cropped to not cover area // that will be tapped. Window behind gets touch, but with flag // AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED - std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100); - nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222}; - parentSurface->mInputInfo.ownerUid = gui::Uid{22222}; + std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface"); + std::unique_ptr<InputSurface> nonTouchableSurface = + makeSurface(100, 100, "Non-Touchable Surface"); + nonTouchableSurface->mInputInfo->editInfo() + ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); + parentSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; + parentSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; nonTouchableSurface->showAt(0, 0); parentSurface->showAt(100, 100); @@ -878,17 +894,20 @@ TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) { } TEST_F(InputSurfacesTest, touch_not_obscured_with_crop) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); surface->showAt(100, 100); // Add non touchable window to cover touchable window, but parent is cropped to avoid covering // the touchable window. Window behind gets touch with no obscured flags. - std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100); - std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100); - nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222}; - parentSurface->mInputInfo.ownerUid = gui::Uid{22222}; + std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface"); + std::unique_ptr<InputSurface> nonTouchableSurface = + makeSurface(100, 100, "Non-Touchable Surface"); + nonTouchableSurface->mInputInfo->editInfo() + ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); + parentSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; + parentSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; nonTouchableSurface->showAt(0, 0); parentSurface->showAt(50, 50); @@ -906,8 +925,9 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_bql) { std::unique_ptr<InputSurface> bufferSurface = InputSurface::makeBufferInputSurface(mComposerClient, 0, 0); - bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - bufferSurface->mInputInfo.ownerUid = gui::Uid{22222}; + bufferSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + bufferSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; surface->showAt(10, 10); bufferSurface->showAt(50, 50, Rect::EMPTY_RECT); @@ -921,8 +941,9 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_blast) { std::unique_ptr<BlastInputSurface> bufferSurface = BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0); - bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - bufferSurface->mInputInfo.ownerUid = gui::Uid{22222}; + bufferSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + bufferSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; surface->showAt(10, 10); bufferSurface->showAt(50, 50, Rect::EMPTY_RECT); @@ -964,14 +985,15 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) { } TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); - surface->mInputInfo.ownerUid = gui::Uid{11111}; + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); + surface->mInputInfo->editInfo()->ownerUid = gui::Uid{11111}; surface->doTransaction( [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); surface->showAt(100, 100); - std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100); - obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222}; + std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100, "Obscuring Surface"); + obscuringSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + obscuringSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; obscuringSurface->showAt(100, 100); injectTap(101, 101); surface->assertNoEvent(); @@ -983,14 +1005,15 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) { } TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); - surface->mInputInfo.ownerUid = gui::Uid{11111}; + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); + surface->mInputInfo->editInfo()->ownerUid = gui::Uid{11111}; surface->doTransaction( [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); surface->showAt(100, 100); - std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100); - obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); - obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222}; + std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100, "Obscuring Surface"); + obscuringSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, + true); + obscuringSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222}; obscuringSurface->showAt(190, 190); injectTap(101, 101); @@ -1004,10 +1027,10 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { } TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { - std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300); + std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300, "Parent Surface"); parentSurface->showAt(0, 0, Rect(0, 0, 300, 300)); - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); surface->showAt(100, 100); surface->doTransaction([&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); @@ -1026,10 +1049,10 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { } TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { - std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300); + std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300, "Parent Surface"); parentSurface->showAt(0, 0, Rect(0, 0, 300, 300)); - std::unique_ptr<InputSurface> surface = makeSurface(100, 100); + std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface"); surface->doTransaction([&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); t.reparent(sc, parentSurface->mSurfaceControl); @@ -1054,7 +1077,7 @@ TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) { BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0); surface->showAt(100, 100); - bufferSurface->mInputInfo.touchableRegion.orSelf(Rect(0, 0, 200, 200)); + bufferSurface->mInputInfo->editInfo()->touchableRegion.orSelf(Rect(0, 0, 200, 200)); bufferSurface->showAt(100, 100, Rect::EMPTY_RECT); injectTap(101, 101); @@ -1092,13 +1115,15 @@ TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) { */ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_crop) { std::unique_ptr<InputSurface> parentContainer = - InputSurface::makeContainerInputSurface(mComposerClient, 0, 0); + InputSurface::makeContainerInputSurface(mComposerClient, 0, 0, + "Parent Container Surface"); std::unique_ptr<InputSurface> containerSurface = - InputSurface::makeContainerInputSurface(mComposerClient, 100, 100); + InputSurface::makeContainerInputSurface(mComposerClient, 100, 100, + "Test Container Surface"); containerSurface->doTransaction( [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); - containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true; - containerSurface->mInputInfo.touchableRegionCropHandle = nullptr; + containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true; + containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle = nullptr; parentContainer->showAt(10, 10, Rect(0, 0, 20, 20)); containerSurface->showAt(10, 10, Rect(0, 0, 5, 5)); @@ -1116,14 +1141,22 @@ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_ * in its parent's touchable region. The input events should be in the layer's coordinate space. */ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_null_crop) { + std::unique_ptr<InputSurface> bgContainer = + InputSurface::makeContainerInputSurface(mComposerClient, 0, 0, + "Background Container Surface"); std::unique_ptr<InputSurface> parentContainer = - InputSurface::makeContainerInputSurface(mComposerClient, 0, 0); + InputSurface::makeContainerInputSurface(mComposerClient, 0, 0, + "Parent Container Surface"); std::unique_ptr<InputSurface> containerSurface = - InputSurface::makeContainerInputSurface(mComposerClient, 100, 100); + InputSurface::makeContainerInputSurface(mComposerClient, 100, 100, + "Test Container Surface"); containerSurface->doTransaction( [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); - containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true; - containerSurface->mInputInfo.touchableRegionCropHandle = nullptr; + containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true; + containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle = nullptr; + parentContainer->doTransaction( + [&](auto& t, auto& sc) { t.reparent(sc, bgContainer->mSurfaceControl); }); + bgContainer->showAt(0, 0, Rect(0, 0, 100, 100)); parentContainer->showAt(10, 10, Rect(0, 0, 20, 20)); containerSurface->showAt(10, 10, Rect::INVALID_RECT); @@ -1142,13 +1175,13 @@ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_nul */ TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) { std::unique_ptr<InputSurface> cropLayer = - InputSurface::makeContainerInputSurface(mComposerClient, 0, 0); + InputSurface::makeContainerInputSurface(mComposerClient, 0, 0, "Crop Layer Surface"); cropLayer->showAt(50, 50, Rect(0, 0, 20, 20)); std::unique_ptr<InputSurface> containerSurface = - InputSurface::makeContainerInputSurface(mComposerClient, 100, 100); - containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true; - containerSurface->mInputInfo.touchableRegionCropHandle = + InputSurface::makeContainerInputSurface(mComposerClient, 100, 100, "Container Surface"); + containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true; + containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle = cropLayer->mSurfaceControl->getHandle(); containerSurface->showAt(10, 10, Rect::INVALID_RECT); @@ -1194,9 +1227,17 @@ public: sp<IGraphicBufferConsumer> consumer; sp<IGraphicBufferProducer> producer; BufferQueue::createBufferQueue(&producer, &consumer); - consumer->setConsumerName(String8("Virtual disp consumer")); + consumer->setConsumerName(String8("Virtual disp consumer (MultiDisplayTests)")); consumer->setDefaultBufferSize(width, height); - mProducers.push_back(producer); + + class StubConsumerListener : public BnConsumerListener { + virtual void onFrameAvailable(const BufferItem&) override {} + virtual void onBuffersReleased() override {} + virtual void onSidebandStreamChanged() override {} + }; + + consumer->consumerConnect(sp<StubConsumerListener>::make(), true); + mBufferQueues.push_back({consumer, producer}); std::string name = "VirtualDisplay"; name += std::to_string(mVirtualDisplays.size()); @@ -1207,13 +1248,13 @@ public: t.setDisplayLayerStack(token, layerStack); t.setDisplayProjection(token, ui::ROTATION_0, {0, 0, width, height}, {offsetX, offsetY, offsetX + width, offsetY + height}); - t.apply(true); + t.apply(/*synchronously=*/true); mVirtualDisplays.push_back(token); } std::vector<sp<IBinder>> mVirtualDisplays; - std::vector<sp<IGraphicBufferProducer>> mProducers; + std::vector<std::tuple<sp<IGraphicBufferConsumer>, sp<IGraphicBufferProducer>>> mBufferQueues; }; TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) { diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp index 04bfb28f65..9ffe91f460 100644 --- a/libs/gui/tests/FrameRateUtilsTest.cpp +++ b/libs/gui/tests/FrameRateUtilsTest.cpp @@ -34,7 +34,7 @@ TEST(FrameRateUtilsTest, ValidateFrameRate) { ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, "")); EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_GTE, + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); // Privileged APIs. @@ -62,7 +62,7 @@ TEST(FrameRateUtilsTest, ValidateFrameRate) { // Invalid compatibility. EXPECT_FALSE( ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_FALSE(ValidateFrameRate(60.0f, 3, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); // Invalid change frame rate strategy. if (flags::bq_setframerate()) { diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp index f98437b4f8..8fea689c91 100644 --- a/libs/gui/tests/SamplingDemo.cpp +++ b/libs/gui/tests/SamplingDemo.cpp @@ -46,7 +46,8 @@ public: SurfaceComposerClient::Transaction{} .setLayer(mButton, 0x7fffffff) - .setCrop(mButton, {0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING}) + .setCrop(mButton, + Rect{0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING}) .setPosition(mButton, samplingArea.left + BUTTON_PADDING, samplingArea.top + BUTTON_PADDING) .setColor(mButton, half3{1, 1, 1}) @@ -59,7 +60,8 @@ public: SurfaceComposerClient::Transaction{} .setLayer(mButtonBlend, 0x7ffffffe) .setCrop(mButtonBlend, - {0, 0, width - 2 * SAMPLE_AREA_PADDING, height - 2 * SAMPLE_AREA_PADDING}) + Rect{0, 0, width - 2 * SAMPLE_AREA_PADDING, + height - 2 * SAMPLE_AREA_PADDING}) .setPosition(mButtonBlend, samplingArea.left + SAMPLE_AREA_PADDING, samplingArea.top + SAMPLE_AREA_PADDING) .setColor(mButtonBlend, half3{1, 1, 1}) @@ -75,7 +77,7 @@ public: SurfaceComposerClient::Transaction{} .setLayer(mSamplingArea, 0x7ffffffd) - .setCrop(mSamplingArea, {0, 0, 100, 32}) + .setCrop(mSamplingArea, Rect{0, 0, 100, 32}) .setPosition(mSamplingArea, 490, 1606) .setColor(mSamplingArea, half3{0, 1, 0}) .setAlpha(mSamplingArea, 0.1) diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp index f34b03eade..1c439cdb45 100644 --- a/libs/gui/tests/StreamSplitter_test.cpp +++ b/libs/gui/tests/StreamSplitter_test.cpp @@ -95,8 +95,7 @@ TEST_F(StreamSplitterTest, OneInputOneOutput) { ASSERT_EQ(*dataOut, TEST_DATA); ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); - ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, - EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE)); // This should succeed even with allocation disabled since it will have // received the buffer back from the output BufferQueue @@ -168,9 +167,9 @@ TEST_F(StreamSplitterTest, OneInputMultipleOutputs) { ASSERT_EQ(*dataOut, TEST_DATA); ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); - ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mSlot, - item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, - Fence::NO_FENCE)); + ASSERT_EQ(OK, + outputConsumers[output]->releaseBuffer(item.mSlot, item.mFrameNumber, + Fence::NO_FENCE)); } // This should succeed even with allocation disabled since it will have diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 88893b64ba..76362ff272 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -21,6 +21,7 @@ #include <gtest/gtest.h> #include <SurfaceFlingerProperties.h> +#include <android/gui/IActivePictureListener.h> #include <android/gui/IDisplayEventConnection.h> #include <android/gui/ISurfaceComposer.h> #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h> @@ -201,9 +202,9 @@ protected: releasedItems.resize(1+extraDiscardedBuffers); for (size_t i = 0; i < releasedItems.size(); i++) { ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0)); - ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot, - releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, - Fence::NO_FENCE)); + ASSERT_EQ(NO_ERROR, + consumer->releaseBuffer(releasedItems[i].mSlot, releasedItems[i].mFrameNumber, + Fence::NO_FENCE)); } int32_t expectedReleaseCb = (enableReleasedCb ? releasedItems.size() : 0); if (hasSurfaceListener) { @@ -1015,6 +1016,15 @@ public: return binder::Status::ok(); } + binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>&) { + return binder::Status::ok(); + } + + binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& /*display*/, + int32_t* /*outMaxProfiles*/) { + return binder::Status::ok(); + } + protected: IBinder* onAsBinder() override { return nullptr; } diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp index 84c2a6ac71..2cf7081ede 100644 --- a/libs/gui/view/Surface.cpp +++ b/libs/gui/view/Surface.cpp @@ -121,6 +121,42 @@ String16 Surface::readMaybeEmptyString16(const Parcel* parcel) { return str.value_or(String16()); } +#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES +Surface Surface::fromSurface(const sp<android::Surface>& surface) { + if (surface == nullptr) { + ALOGE("%s: Error: view::Surface::fromSurface failed due to null surface.", __FUNCTION__); + return Surface(); + } + Surface s; + s.name = String16(surface->getConsumerName()); + s.graphicBufferProducer = surface->getIGraphicBufferProducer(); + s.surfaceControlHandle = surface->getSurfaceControlHandle(); + return s; +} + +sp<android::Surface> Surface::toSurface() const { + if (graphicBufferProducer == nullptr) return nullptr; + return new android::Surface(graphicBufferProducer, false, surfaceControlHandle); +} + +status_t Surface::getUniqueId(uint64_t* out_id) const { + if (graphicBufferProducer == nullptr) { + ALOGE("android::viewSurface::getUniqueId() failed because it's not initialized."); + return UNEXPECTED_NULL; + } + status_t status = graphicBufferProducer->getUniqueId(out_id); + if (status != OK) { + ALOGE("android::viewSurface::getUniqueId() failed."); + return status; + } + return OK; +} + +bool Surface::isEmpty() const { + return graphicBufferProducer == nullptr; +} +#endif + std::string Surface::toString() const { std::stringstream out; out << name; diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 35d704a1b4..389fb7f6ab 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -217,6 +217,7 @@ cc_library { ], srcs: [ "AccelerationCurve.cpp", + "CoordinateFilter.cpp", "Input.cpp", "InputConsumer.cpp", "InputConsumerNoResampling.cpp", @@ -230,6 +231,7 @@ cc_library { "KeyLayoutMap.cpp", "MotionPredictor.cpp", "MotionPredictorMetricsManager.cpp", + "OneEuroFilter.cpp", "PrintTools.cpp", "PropertyMap.cpp", "Resampler.cpp", diff --git a/libs/input/CoordinateFilter.cpp b/libs/input/CoordinateFilter.cpp new file mode 100644 index 0000000000..a32685bd53 --- /dev/null +++ b/libs/input/CoordinateFilter.cpp @@ -0,0 +1,31 @@ +/** + * 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. + */ + +#define LOG_TAG "CoordinateFilter" + +#include <input/CoordinateFilter.h> + +namespace android { + +CoordinateFilter::CoordinateFilter(float minCutoffFreq, float beta) + : mXFilter{minCutoffFreq, beta}, mYFilter{minCutoffFreq, beta} {} + +void CoordinateFilter::filter(std::chrono::nanoseconds timestamp, PointerCoords& coords) { + coords.setAxisValue(AMOTION_EVENT_AXIS_X, mXFilter.filter(timestamp, coords.getX())); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, mYFilter.filter(timestamp, coords.getY())); +} + +} // namespace android diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index a2bb3453fe..65a088eb6d 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -685,11 +685,12 @@ void MotionEvent::setCursorPosition(float x, float y) { const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const { if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) { - LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this; + LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " + << safeDump(); } const size_t position = getHistorySize() * getPointerCount() + pointerIndex; if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) { - LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this; + LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump(); } return &mSamplePointerCoords[position]; } @@ -705,15 +706,16 @@ float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const { const PointerCoords* MotionEvent::getHistoricalRawPointerCoords( size_t pointerIndex, size_t historicalIndex) const { if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) { - LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this; + LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " + << safeDump(); } if (CC_UNLIKELY(historicalIndex < 0 || historicalIndex > getHistorySize())) { LOG(FATAL) << __func__ << ": Invalid historical index " << historicalIndex << " for " - << *this; + << safeDump(); } const size_t position = historicalIndex * getPointerCount() + pointerIndex; if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) { - LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this; + LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump(); } return &mSamplePointerCoords[position]; } @@ -1144,6 +1146,53 @@ bool MotionEvent::operator==(const android::MotionEvent& o) const { // clang-format on } +std::string MotionEvent::safeDump() const { + std::stringstream out; + // Field names have the m prefix here to make it easy to distinguish safeDump output from + // operator<< output in logs. + out << "MotionEvent { mAction=" << MotionEvent::actionToString(mAction); + if (mActionButton != 0) { + out << ", mActionButton=" << mActionButton; + } + if (mButtonState != 0) { + out << ", mButtonState=" << mButtonState; + } + if (mClassification != MotionClassification::NONE) { + out << ", mClassification=" << motionClassificationToString(mClassification); + } + if (mMetaState != 0) { + out << ", mMetaState=" << mMetaState; + } + if (mFlags != 0) { + out << ", mFlags=0x" << std::hex << mFlags << std::dec; + } + if (mEdgeFlags != 0) { + out << ", mEdgeFlags=" << mEdgeFlags; + } + out << ", mDownTime=" << mDownTime; + out << ", mDeviceId=" << mDeviceId; + out << ", mSource=" << inputEventSourceToString(mSource); + out << ", mDisplayId=" << mDisplayId; + out << ", mEventId=0x" << std::hex << mId << std::dec; + // Since we're not assuming the data is at all valid, we also limit the number of items that + // might be printed from vectors, in case the vector's size field is corrupted. + out << ", mPointerProperties=(" << mPointerProperties.size() << ")["; + for (size_t i = 0; i < mPointerProperties.size() && i < MAX_POINTERS; i++) { + out << (i > 0 ? ", " : "") << mPointerProperties.at(i); + } + out << "], mSampleEventTimes=(" << mSampleEventTimes.size() << ")["; + for (size_t i = 0; i < mSampleEventTimes.size() && i < 256; i++) { + out << (i > 0 ? ", " : "") << mSampleEventTimes.at(i); + } + out << "], mSamplePointerCoords=(" << mSamplePointerCoords.size() << ")["; + for (size_t i = 0; i < mSamplePointerCoords.size() && i < MAX_POINTERS; i++) { + const PointerCoords& coords = mSamplePointerCoords.at(i); + out << (i > 0 ? ", " : "") << "(" << coords.getX() << ", " << coords.getY() << ")"; + } + out << "] }"; + return out.str(); +} + std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { out << "MotionEvent { action=" << MotionEvent::actionToString(event.getAction()); if (event.getActionButton() != 0) { diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp index cdbc1869c3..cd8582182a 100644 --- a/libs/input/InputConsumerNoResampling.cpp +++ b/libs/input/InputConsumerNoResampling.cpp @@ -17,8 +17,6 @@ #define LOG_TAG "InputConsumerNoResampling" #define ATRACE_TAG ATRACE_TAG_INPUT -#include <chrono> - #include <inttypes.h> #include <android-base/logging.h> @@ -39,6 +37,8 @@ namespace { using std::chrono::nanoseconds; +using android::base::Result; + /** * Log debug messages relating to the consumer end of the transport channel. * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart) @@ -170,23 +170,23 @@ InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTim return msg; } -bool isPointerEvent(const MotionEvent& motionEvent) { - return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER; +std::ostream& operator<<(std::ostream& out, const InputMessage& msg) { + out << ftl::enum_string(msg.header.type); + return out; } -} // namespace -using android::base::Result; +} // namespace // --- InputConsumerNoResampling --- -InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel, - sp<Looper> looper, - InputConsumerCallbacks& callbacks, - std::unique_ptr<Resampler> resampler) +InputConsumerNoResampling::InputConsumerNoResampling( + const std::shared_ptr<InputChannel>& channel, sp<Looper> looper, + InputConsumerCallbacks& callbacks, + std::function<std::unique_ptr<Resampler>()> resamplerCreator) : mChannel{channel}, mLooper{looper}, mCallbacks{callbacks}, - mResampler{std::move(resampler)}, + mResamplerCreator{std::move(resamplerCreator)}, mFdEvents(0) { LOG_ALWAYS_FATAL_IF(mLooper == nullptr); mCallback = sp<LooperEventCallback>::make( @@ -199,12 +199,31 @@ InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<Input InputConsumerNoResampling::~InputConsumerNoResampling() { ensureCalledOnLooperThread(__func__); - consumeBatchedInputEvents(/*requestedFrameTime=*/std::nullopt); + // If there are any remaining unread batches, send an ack for them and don't deliver + // them to callbacks. + for (auto& [_, batches] : mBatches) { + while (!batches.empty()) { + finishInputEvent(batches.front().header.seq, /*handled=*/false); + batches.pop(); + } + } + while (!mOutboundQueue.empty()) { processOutboundEvents(); // This is our last chance to ack the events. If we don't ack them here, we will get an ANR, // so keep trying to send the events as long as they are present in the queue. } + // However, it is still up to the app to finish any events that have already been delivered + // to the callbacks. If we wanted to change that behaviour and auto-finish all unfinished events + // that were already sent to callbacks, we could potentially loop through "mConsumeTimes" + // instead. We can't use "mBatchedSequenceNumbers" for this purpose, because it only contains + // batchable (i.e., ACTION_MOVE) events that were sent to the callbacks. + const size_t unfinishedEvents = mConsumeTimes.size(); + LOG_IF(INFO, unfinishedEvents != 0) + << getName() << " has " << unfinishedEvents << " unfinished event(s)"; + // Remove the fd from epoll, so that Looper does not call 'handleReceiveCallback' anymore. + // This must be done at the end of the destructor; otherwise, some of the other functions may + // call 'setFdEvents' as a side-effect, thus adding the fd back to the epoll set of the looper. setFdEvents(0); } @@ -259,6 +278,15 @@ void InputConsumerNoResampling::processOutboundEvents() { return; // try again later } + if (result == DEAD_OBJECT) { + // If there's no one to receive events in the channel, there's no point in sending them. + // Drop all outbound events. + LOG(INFO) << "Channel " << mChannel->getName() << " died. Dropping outbound event " + << outboundMsg; + mOutboundQueue.pop(); + setFdEvents(0); + continue; + } // Some other error. Give up LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName() << "'. status=" << statusToString(result) << "(" << result << ")"; @@ -319,7 +347,6 @@ void InputConsumerNoResampling::setFdEvents(int events) { } void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) { - // TODO(b/297226446) : add resampling for (const InputMessage& msg : messages) { if (msg.header.type == InputMessage::Type::MOTION) { const int32_t action = msg.body.motion.action; @@ -329,12 +356,32 @@ void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messa action == AMOTION_EVENT_ACTION_HOVER_MOVE) && (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) || isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK)); + + const bool canResample = (mResamplerCreator != nullptr) && + (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER)); + if (canResample) { + if (action == AMOTION_EVENT_ACTION_DOWN) { + if (std::unique_ptr<Resampler> resampler = mResamplerCreator(); + resampler != nullptr) { + const auto [_, inserted] = + mResamplers.insert(std::pair(deviceId, std::move(resampler))); + LOG_IF(WARNING, !inserted) << deviceId << "already exists in mResamplers"; + } + } + } + if (batchableEvent) { // add it to batch mBatches[deviceId].emplace(msg); } else { // consume all pending batches for this device immediately - consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt); + consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/ + std::numeric_limits<nsecs_t>::max()); + if (canResample && + (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL)) { + LOG_IF(INFO, mResamplers.erase(deviceId) == 0) + << deviceId << "does not exist in mResamplers"; + } handleMessage(msg); } } else { @@ -452,13 +499,22 @@ void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const { } std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>> -InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrameTime, +InputConsumerNoResampling::createBatchedMotionEvent(const std::optional<nsecs_t> requestedFrameTime, std::queue<InputMessage>& messages) { std::unique_ptr<MotionEvent> motionEvent; std::optional<uint32_t> firstSeqForBatch; - const nanoseconds resampleLatency = - (mResampler != nullptr) ? mResampler->getResampleLatency() : nanoseconds{0}; - const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency; + + LOG_IF(FATAL, messages.empty()) << "messages queue is empty!"; + const DeviceId deviceId = messages.front().body.motion.deviceId; + const auto resampler = mResamplers.find(deviceId); + const nanoseconds resampleLatency = (resampler != mResamplers.cend()) + ? resampler->second->getResampleLatency() + : nanoseconds{0}; + // When batching is not enabled, we want to consume all events. That's equivalent to having an + // infinite requestedFrameTime. + const nanoseconds adjustedFrameTime = (requestedFrameTime.has_value()) + ? (nanoseconds{*requestedFrameTime} - resampleLatency) + : nanoseconds{std::numeric_limits<nsecs_t>::max()}; while (!messages.empty() && (messages.front().body.motion.eventTime <= adjustedFrameTime.count())) { @@ -474,31 +530,31 @@ InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrame } messages.pop(); } + // Check if resampling should be performed. - if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) { - InputMessage* futureSample = nullptr; - if (!messages.empty()) { - futureSample = &messages.front(); - } - mResampler->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent, - futureSample); + InputMessage* futureSample = nullptr; + if (!messages.empty()) { + futureSample = &messages.front(); + } + if ((motionEvent != nullptr) && (resampler != mResamplers.cend()) && + (requestedFrameTime.has_value())) { + resampler->second->resampleMotionEvent(nanoseconds{*requestedFrameTime}, *motionEvent, + futureSample); } + return std::make_pair(std::move(motionEvent), firstSeqForBatch); } bool InputConsumerNoResampling::consumeBatchedInputEvents( std::optional<DeviceId> deviceId, std::optional<nsecs_t> requestedFrameTime) { ensureCalledOnLooperThread(__func__); - // When batching is not enabled, we want to consume all events. That's equivalent to having an - // infinite requestedFrameTime. - requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max()); bool producedEvents = false; for (auto deviceIdIter = (deviceId.has_value()) ? (mBatches.find(*deviceId)) : (mBatches.begin()); deviceIdIter != mBatches.cend(); ++deviceIdIter) { std::queue<InputMessage>& messages = deviceIdIter->second; - auto [motion, firstSeqForBatch] = createBatchedMotionEvent(*requestedFrameTime, messages); + auto [motion, firstSeqForBatch] = createBatchedMotionEvent(requestedFrameTime, messages); if (motion != nullptr) { LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value()); mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch); diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index c9030312f9..4a6f66e058 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -191,7 +191,9 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mKeyboardLayoutInfo(other.mKeyboardLayoutInfo), mSources(other.mSources), mKeyboardType(other.mKeyboardType), - mKeyCharacterMap(other.mKeyCharacterMap), + mKeyCharacterMap(other.mKeyCharacterMap + ? std::make_unique<KeyCharacterMap>(*other.mKeyCharacterMap) + : nullptr), mUsiVersion(other.mUsiVersion), mAssociatedDisplayId(other.mAssociatedDisplayId), mEnabled(other.mEnabled), @@ -204,6 +206,34 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mLights(other.mLights), mViewBehavior(other.mViewBehavior) {} +InputDeviceInfo& InputDeviceInfo::operator=(const InputDeviceInfo& other) { + mId = other.mId; + mGeneration = other.mGeneration; + mControllerNumber = other.mControllerNumber; + mIdentifier = other.mIdentifier; + mAlias = other.mAlias; + mIsExternal = other.mIsExternal; + mHasMic = other.mHasMic; + mKeyboardLayoutInfo = other.mKeyboardLayoutInfo; + mSources = other.mSources; + mKeyboardType = other.mKeyboardType; + mKeyCharacterMap = other.mKeyCharacterMap + ? std::make_unique<KeyCharacterMap>(*other.mKeyCharacterMap) + : nullptr; + mUsiVersion = other.mUsiVersion; + mAssociatedDisplayId = other.mAssociatedDisplayId; + mEnabled = other.mEnabled; + mHasVibrator = other.mHasVibrator; + mHasBattery = other.mHasBattery; + mHasButtonUnderPad = other.mHasButtonUnderPad; + mHasSensor = other.mHasSensor; + mMotionRanges = other.mMotionRanges; + mSensors = other.mSensors; + mLights = other.mLights; + mViewBehavior = other.mViewBehavior; + return *this; +} + InputDeviceInfo::~InputDeviceInfo() { } diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 8db0ca588b..b537feb68f 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -350,7 +350,26 @@ namespace android { DEFINE_KEYCODE(MACRO_3), \ DEFINE_KEYCODE(MACRO_4), \ DEFINE_KEYCODE(EMOJI_PICKER), \ - DEFINE_KEYCODE(SCREENSHOT) + DEFINE_KEYCODE(SCREENSHOT), \ + DEFINE_KEYCODE(DICTATE), \ + DEFINE_KEYCODE(NEW), \ + DEFINE_KEYCODE(CLOSE), \ + DEFINE_KEYCODE(DO_NOT_DISTURB), \ + DEFINE_KEYCODE(PRINT), \ + DEFINE_KEYCODE(LOCK), \ + DEFINE_KEYCODE(FULLSCREEN), \ + DEFINE_KEYCODE(F13), \ + DEFINE_KEYCODE(F14), \ + DEFINE_KEYCODE(F15), \ + DEFINE_KEYCODE(F16), \ + DEFINE_KEYCODE(F17), \ + DEFINE_KEYCODE(F18), \ + DEFINE_KEYCODE(F19),\ + DEFINE_KEYCODE(F20), \ + DEFINE_KEYCODE(F21), \ + DEFINE_KEYCODE(F22), \ + DEFINE_KEYCODE(F23), \ + DEFINE_KEYCODE(F24) // 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/InputTransport.cpp b/libs/input/InputTransport.cpp index 77dcaa9ef2..6a55726db1 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -583,15 +583,6 @@ status_t InputPublisher::publishMotionEvent( StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)", mChannel->getName().c_str(), MotionEvent::actionToString(action).c_str())); - if (verifyEvents()) { - Result<void> result = - mInputVerifier.processMovement(deviceId, source, action, pointerCount, - pointerProperties, pointerCoords, flags); - if (!result.ok()) { - LOG(ERROR) << "Bad stream: " << result.error(); - return BAD_VALUE; - } - } if (debugTransportPublisher()) { std::string transformString; transform.dump(transformString, "transform", " "); @@ -657,8 +648,18 @@ status_t InputPublisher::publishMotionEvent( msg.body.motion.pointers[i].properties = pointerProperties[i]; msg.body.motion.pointers[i].coords = pointerCoords[i]; } + const status_t status = mChannel->sendMessage(&msg); - return mChannel->sendMessage(&msg); + if (status == OK && verifyEvents()) { + Result<void> result = + mInputVerifier.processMovement(deviceId, source, action, pointerCount, + pointerProperties, pointerCoords, flags); + if (!result.ok()) { + LOG(ERROR) << "Bad stream: " << result.error(); + return BAD_VALUE; + } + } + return status; } status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) { diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index b0563abaf7..90d29dd190 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -84,15 +84,15 @@ static String8 toString(const char16_t* chars, size_t numChars) { KeyCharacterMap::KeyCharacterMap(const std::string& filename) : mLoadFileName(filename) {} -base::Result<std::shared_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename, +base::Result<std::unique_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename, Format format) { Tokenizer* tokenizer; status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer); if (status) { return Errorf("Error {} opening key character map file {}.", status, filename.c_str()); } - std::shared_ptr<KeyCharacterMap> map = - std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap(filename)); + std::unique_ptr<KeyCharacterMap> map = + std::unique_ptr<KeyCharacterMap>(new KeyCharacterMap(filename)); if (!map.get()) { ALOGE("Error allocating key character map."); return Errorf("Error allocating key character map."); @@ -365,6 +365,17 @@ int32_t KeyCharacterMap::applyKeyRemapping(int32_t fromKeyCode) const { return toKeyCode; } +std::vector<int32_t> KeyCharacterMap::findKeyCodesMappedToKeyCode(int32_t toKeyCode) const { + std::vector<int32_t> fromKeyCodes; + + for (const auto& [from, to] : mKeyRemapping) { + if (toKeyCode == to) { + fromKeyCodes.push_back(from); + } + } + return fromKeyCodes; +} + std::pair<int32_t, int32_t> KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCode, int32_t fromMetaState) const { int32_t toKeyCode = fromKeyCode; diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp index 0c2c7be582..2a83919283 100644 --- a/libs/input/KeyboardClassifier.cpp +++ b/libs/input/KeyboardClassifier.cpp @@ -57,14 +57,14 @@ void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId, uint32_t deviceClasses) { if (mRustClassifier) { RustInputDeviceIdentifier rustIdentifier; - rustIdentifier.name = identifier.name; - rustIdentifier.location = identifier.location; - rustIdentifier.unique_id = identifier.uniqueId; + rustIdentifier.name = rust::String::lossy(identifier.name); + rustIdentifier.location = rust::String::lossy(identifier.location); + rustIdentifier.unique_id = rust::String::lossy(identifier.uniqueId); rustIdentifier.bus = identifier.bus; rustIdentifier.vendor = identifier.vendor; rustIdentifier.product = identifier.product; rustIdentifier.version = identifier.version; - rustIdentifier.descriptor = identifier.descriptor; + rustIdentifier.descriptor = rust::String::lossy(identifier.descriptor); android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId, rustIdentifier, deviceClasses); } else { diff --git a/libs/input/OneEuroFilter.cpp b/libs/input/OneEuroFilter.cpp new file mode 100644 index 0000000000..7b0d104da1 --- /dev/null +++ b/libs/input/OneEuroFilter.cpp @@ -0,0 +1,87 @@ +/** + * 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. + */ + +#define LOG_TAG "OneEuroFilter" + +#include <chrono> +#include <cmath> + +#include <android-base/logging.h> +#include <input/CoordinateFilter.h> + +namespace android { +namespace { + +using namespace std::literals::chrono_literals; + +const float kHertzPerGigahertz = 1E9f; +const float kGigahertzPerHertz = 1E-9f; + +// filteredSpeed's units are position per nanosecond. beta's units are 1 / position. +inline float cutoffFreq(float minCutoffFreq, float beta, float filteredSpeed) { + return kHertzPerGigahertz * + ((minCutoffFreq * kGigahertzPerHertz) + beta * std::abs(filteredSpeed)); +} + +inline float smoothingFactor(std::chrono::nanoseconds samplingPeriod, float cutoffFreq) { + const float constant = 2.0f * M_PI * samplingPeriod.count() * (cutoffFreq * kGigahertzPerHertz); + return constant / (constant + 1); +} + +inline float lowPassFilter(float rawValue, float prevFilteredValue, float smoothingFactor) { + return smoothingFactor * rawValue + (1 - smoothingFactor) * prevFilteredValue; +} + +} // namespace + +OneEuroFilter::OneEuroFilter(float minCutoffFreq, float beta, float speedCutoffFreq) + : mMinCutoffFreq{minCutoffFreq}, mBeta{beta}, mSpeedCutoffFreq{speedCutoffFreq} {} + +float OneEuroFilter::filter(std::chrono::nanoseconds timestamp, float rawPosition) { + LOG_IF(FATAL, mPrevTimestamp.has_value() && (*mPrevTimestamp >= timestamp)) + << "Timestamp must be greater than mPrevTimestamp. Timestamp: " << timestamp.count() + << "ns. mPrevTimestamp: " << mPrevTimestamp->count() << "ns"; + + const std::chrono::nanoseconds samplingPeriod = + (mPrevTimestamp.has_value()) ? (timestamp - *mPrevTimestamp) : 1s; + + const float rawVelocity = (mPrevFilteredPosition.has_value()) + ? ((rawPosition - *mPrevFilteredPosition) / (samplingPeriod.count())) + : 0.0f; + + const float speedSmoothingFactor = smoothingFactor(samplingPeriod, mSpeedCutoffFreq); + + const float filteredVelocity = (mPrevFilteredVelocity.has_value()) + ? lowPassFilter(rawVelocity, *mPrevFilteredVelocity, speedSmoothingFactor) + : rawVelocity; + + const float positionCutoffFreq = cutoffFreq(mMinCutoffFreq, mBeta, filteredVelocity); + + const float positionSmoothingFactor = smoothingFactor(samplingPeriod, positionCutoffFreq); + + const float filteredPosition = (mPrevFilteredPosition.has_value()) + ? lowPassFilter(rawPosition, *mPrevFilteredPosition, positionSmoothingFactor) + : rawPosition; + + mPrevTimestamp = timestamp; + mPrevRawPosition = rawPosition; + mPrevFilteredVelocity = filteredVelocity; + mPrevFilteredPosition = filteredPosition; + + return filteredPosition; +} + +} // namespace android diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp index 51fadf8ec1..3ab132d550 100644 --- a/libs/input/Resampler.cpp +++ b/libs/input/Resampler.cpp @@ -18,6 +18,8 @@ #include <algorithm> #include <chrono> +#include <iomanip> +#include <ostream> #include <android-base/logging.h> #include <android-base/properties.h> @@ -26,10 +28,7 @@ #include <input/Resampler.h> #include <utils/Timers.h> -using std::chrono::nanoseconds; - namespace android { - namespace { const bool IS_DEBUGGABLE_BUILD = @@ -39,6 +38,11 @@ const bool IS_DEBUGGABLE_BUILD = true; #endif +/** + * Log debug messages about timestamp and coordinates of event resampling. + * Enable this via "adb shell setprop log.tag.LegacyResamplerResampling DEBUG" + * (requires restart) + */ bool debugResampling() { if (!IS_DEBUGGABLE_BUILD) { static const bool DEBUG_TRANSPORT_RESAMPLING = @@ -49,6 +53,8 @@ bool debugResampling() { return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO); } +using std::chrono::nanoseconds; + constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5}; constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2}; @@ -75,6 +81,31 @@ PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoor resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha)); return resampledCoords; } + +bool equalXY(const PointerCoords& a, const PointerCoords& b) { + return (a.getX() == b.getX()) && (a.getY() == b.getY()); +} + +void setMotionEventPointerCoords(MotionEvent& motionEvent, size_t sampleIndex, size_t pointerIndex, + const PointerCoords& pointerCoords) { + // Ideally, we should not cast away const. In this particular case, it's safe to cast away const + // and dereference getHistoricalRawPointerCoords returned pointer because motionEvent is a + // nonconst reference to a MotionEvent object, so mutating the object should not be undefined + // behavior; moreover, the invoked method guarantees to return a valid pointer. Otherwise, it + // fatally logs. Alternatively, we could've created a new MotionEvent from scratch, but this + // approach is simpler and more efficient. + PointerCoords& motionEventCoords = const_cast<PointerCoords&>( + *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex))); + motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoords.getX()); + motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoords.getY()); + motionEventCoords.isResampled = pointerCoords.isResampled; +} + +std::ostream& operator<<(std::ostream& os, const PointerCoords& pointerCoords) { + os << "(" << pointerCoords.getX() << ", " << pointerCoords.getY() << ")"; + return os; +} + } // namespace void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) { @@ -82,49 +113,44 @@ void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) { const size_t latestIndex = numSamples - 1; const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0; for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) { - std::vector<Pointer> pointers; - const size_t numPointers = motionEvent.getPointerCount(); - for (size_t pointerIndex = 0; pointerIndex < numPointers; ++pointerIndex) { - // getSamplePointerCoords is the vector representation of a getHistorySize by - // getPointerCount matrix. - const PointerCoords& pointerCoords = - motionEvent.getSamplePointerCoords()[sampleIndex * numPointers + pointerIndex]; - pointers.push_back( - Pointer{*motionEvent.getPointerProperties(pointerIndex), pointerCoords}); + PointerMap pointerMap; + for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); + ++pointerIndex) { + pointerMap.insert(Pointer{*(motionEvent.getPointerProperties(pointerIndex)), + *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, + sampleIndex))}); } mLatestSamples.pushBack( - Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers}); + Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointerMap}); } } LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) { - std::vector<Pointer> pointers; + PointerMap pointerMap; for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) { - pointers.push_back(Pointer{message.body.motion.pointers[i].properties, - message.body.motion.pointers[i].coords}); + pointerMap.insert(Pointer{message.body.motion.pointers[i].properties, + message.body.motion.pointers[i].coords}); } - return Sample{nanoseconds{message.body.motion.eventTime}, pointers}; + return Sample{nanoseconds{message.body.motion.eventTime}, pointerMap}; } bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) { - if (target.pointers.size() > auxiliary.pointers.size()) { - LOG_IF(INFO, debugResampling()) - << "Not resampled. Auxiliary sample has fewer pointers than target sample."; - return false; - } - for (size_t i = 0; i < target.pointers.size(); ++i) { - if (target.pointers[i].properties.id != auxiliary.pointers[i].properties.id) { - LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ID mismatch."; + for (const Pointer& pointer : target.pointerMap) { + const std::optional<Pointer> auxiliaryPointer = + auxiliary.pointerMap.find(PointerMap::PointerId{pointer.properties.id}); + if (!auxiliaryPointer.has_value()) { + LOG_IF(INFO, debugResampling()) + << "Not resampled. Auxiliary sample does not contain all pointers from target."; return false; } - if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) { + if (pointer.properties.toolType != auxiliaryPointer->properties.toolType) { LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch."; return false; } - if (!canResampleTool(target.pointers[i].properties.toolType)) { + if (!canResampleTool(pointer.properties.toolType)) { LOG_IF(INFO, debugResampling()) << "Not resampled. Cannot resample " - << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType."; + << ftl::enum_string(pointer.properties.toolType) << " ToolType."; return false; } } @@ -144,35 +170,40 @@ bool LegacyResampler::canInterpolate(const InputMessage& message) const { const nanoseconds delta = futureSample.eventTime - pastSample.eventTime; if (delta < RESAMPLE_MIN_DELTA) { - LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns."; + LOG_IF(INFO, debugResampling()) + << "Not resampled. Delta is too small: " << std::setprecision(3) + << std::chrono::duration<double, std::milli>{delta}.count() << "ms"; return false; } return true; } std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation( - nanoseconds resampleTime, const InputMessage& futureSample) const { - if (!canInterpolate(futureSample)) { + nanoseconds resampleTime, const InputMessage& futureMessage) const { + if (!canInterpolate(futureMessage)) { return std::nullopt; } LOG_IF(FATAL, mLatestSamples.empty()) << "Not resampled. mLatestSamples must not be empty to interpolate."; const Sample& pastSample = *(mLatestSamples.end() - 1); + const Sample& futureSample = messageToSample(futureMessage); - const nanoseconds delta = - nanoseconds{futureSample.body.motion.eventTime} - pastSample.eventTime; + const nanoseconds delta = nanoseconds{futureSample.eventTime} - pastSample.eventTime; const float alpha = - std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta; - - std::vector<Pointer> resampledPointers; - for (size_t i = 0; i < pastSample.pointers.size(); ++i) { - const PointerCoords& resampledCoords = - calculateResampledCoords(pastSample.pointers[i].coords, - futureSample.body.motion.pointers[i].coords, alpha); - resampledPointers.push_back(Pointer{pastSample.pointers[i].properties, resampledCoords}); + std::chrono::duration<float, std::nano>(resampleTime - pastSample.eventTime) / delta; + + PointerMap resampledPointerMap; + for (const Pointer& pointer : pastSample.pointerMap) { + if (std::optional<Pointer> futureSamplePointer = + futureSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id}); + futureSamplePointer.has_value()) { + const PointerCoords& resampledCoords = + calculateResampledCoords(pointer.coords, futureSamplePointer->coords, alpha); + resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords}); + } } - return Sample{resampleTime, resampledPointers}; + return Sample{resampleTime, resampledPointerMap}; } bool LegacyResampler::canExtrapolate() const { @@ -190,10 +221,14 @@ bool LegacyResampler::canExtrapolate() const { const nanoseconds delta = presentSample.eventTime - pastSample.eventTime; if (delta < RESAMPLE_MIN_DELTA) { - LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns."; + LOG_IF(INFO, debugResampling()) + << "Not resampled. Delta is too small: " << std::setprecision(3) + << std::chrono::duration<double, std::milli>{delta}.count() << "ms"; return false; } else if (delta > RESAMPLE_MAX_DELTA) { - LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns."; + LOG_IF(INFO, debugResampling()) + << "Not resampled. Delta is too large: " << std::setprecision(3) + << std::chrono::duration<double, std::milli>{delta}.count() << "ms"; return false; } return true; @@ -219,20 +254,28 @@ std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation( (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime); LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction) << "Resample time is too far in the future. Adjusting prediction from " - << (resampleTime - presentSample.eventTime) << " to " - << (farthestPrediction - presentSample.eventTime) << "ns."; + << std::setprecision(3) + << std::chrono::duration<double, std::milli>{resampleTime - presentSample.eventTime} + .count() + << "ms to " + << std::chrono::duration<double, std::milli>{farthestPrediction - + presentSample.eventTime} + .count() + << "ms"; const float alpha = - std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) / - delta; - - std::vector<Pointer> resampledPointers; - for (size_t i = 0; i < presentSample.pointers.size(); ++i) { - const PointerCoords& resampledCoords = - calculateResampledCoords(pastSample.pointers[i].coords, - presentSample.pointers[i].coords, alpha); - resampledPointers.push_back(Pointer{presentSample.pointers[i].properties, resampledCoords}); + std::chrono::duration<float, std::nano>(newResampleTime - pastSample.eventTime) / delta; + + PointerMap resampledPointerMap; + for (const Pointer& pointer : presentSample.pointerMap) { + if (std::optional<Pointer> pastSamplePointer = + pastSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id}); + pastSamplePointer.has_value()) { + const PointerCoords& resampledCoords = + calculateResampledCoords(pastSamplePointer->coords, pointer.coords, alpha); + resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords}); + } } - return Sample{newResampleTime, resampledPointers}; + return Sample{newResampleTime, resampledPointerMap}; } inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample, @@ -245,15 +288,87 @@ nanoseconds LegacyResampler::getResampleLatency() const { return RESAMPLE_LATENCY; } -void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent, - const InputMessage* futureSample) { - if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) { - mLatestSamples.clear(); +/** + * The resampler is unaware of ACTION_DOWN. Thus, it needs to constantly check for pointer IDs + * occurrences. This problem could be fixed if the resampler has access to the entire stream of + * MotionEvent actions. That way, both ACTION_DOWN and ACTION_UP will be visible; therefore, + * facilitating pointer tracking between samples. + */ +void LegacyResampler::overwriteMotionEventSamples(MotionEvent& motionEvent) const { + const size_t numSamples = motionEvent.getHistorySize() + 1; + for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) { + overwriteStillPointers(motionEvent, sampleIndex); + overwriteOldPointers(motionEvent, sampleIndex); + } +} + +void LegacyResampler::overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const { + if (!mLastRealSample.has_value() || !mPreviousPrediction.has_value()) { + LOG_IF(INFO, debugResampling()) << "Still pointers not overwritten. Not enough data."; + return; + } + for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); ++pointerIndex) { + const std::optional<Pointer> lastRealPointer = mLastRealSample->pointerMap.find( + PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)}); + const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find( + PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)}); + // This could happen because resampler only receives ACTION_MOVE events. + if (!lastRealPointer.has_value() || !previousPointer.has_value()) { + continue; + } + const PointerCoords& pointerCoords = + *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)); + if (equalXY(pointerCoords, lastRealPointer->coords)) { + LOG_IF(INFO, debugResampling()) + << "Pointer ID: " << motionEvent.getPointerId(pointerIndex) + << " did not move. Overwriting its coordinates from " << pointerCoords << " to " + << previousPointer->coords; + setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex, + previousPointer->coords); + } } - mPreviousDeviceId = motionEvent.getDeviceId(); +} +void LegacyResampler::overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const { + if (!mPreviousPrediction.has_value()) { + LOG_IF(INFO, debugResampling()) << "Old sample not overwritten. Not enough data."; + return; + } + if (nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)} < + mPreviousPrediction->eventTime) { + LOG_IF(INFO, debugResampling()) + << "Motion event sample older than predicted sample. Overwriting event time from " + << std::setprecision(3) + << std::chrono::duration<double, + std::milli>{nanoseconds{motionEvent.getHistoricalEventTime( + sampleIndex)}} + .count() + << "ms to " + << std::chrono::duration<double, std::milli>{mPreviousPrediction->eventTime}.count() + << "ms"; + for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); + ++pointerIndex) { + const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find( + PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)}); + // This could happen because resampler only receives ACTION_MOVE events. + if (!previousPointer.has_value()) { + continue; + } + setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex, + previousPointer->coords); + } + } +} + +void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent, + const InputMessage* futureSample) { const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY; + if (resampleTime.count() == motionEvent.getEventTime()) { + LOG_IF(INFO, debugResampling()) << "Not resampled. Resample time equals motion event time."; + return; + } + updateLatestSamples(motionEvent); const std::optional<Sample> sample = (futureSample != nullptr) @@ -261,6 +376,47 @@ void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& mo : (attemptExtrapolation(resampleTime)); if (sample.has_value()) { addSampleToMotionEvent(*sample, motionEvent); + if (mPreviousPrediction.has_value()) { + overwriteMotionEventSamples(motionEvent); + } + // mPreviousPrediction is only updated whenever extrapolation occurs because extrapolation + // is about predicting upcoming scenarios. + if (futureSample == nullptr) { + mPreviousPrediction = sample; + } + } + LOG_IF(FATAL, mLatestSamples.empty()) << "mLatestSamples must contain at least one sample."; + mLastRealSample = *(mLatestSamples.end() - 1); +} + +// --- FilteredLegacyResampler --- + +FilteredLegacyResampler::FilteredLegacyResampler(float minCutoffFreq, float beta) + : mResampler{}, mMinCutoffFreq{minCutoffFreq}, mBeta{beta} {} + +void FilteredLegacyResampler::resampleMotionEvent(std::chrono::nanoseconds requestedFrameTime, + MotionEvent& motionEvent, + const InputMessage* futureSample) { + mResampler.resampleMotionEvent(requestedFrameTime, motionEvent, futureSample); + const size_t numSamples = motionEvent.getHistorySize() + 1; + for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) { + for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); + ++pointerIndex) { + const int32_t pointerId = motionEvent.getPointerProperties(pointerIndex)->id; + const nanoseconds eventTime = + nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}; + // Refer to the static function `setMotionEventPointerCoords` for a justification of + // casting away const. + PointerCoords& pointerCoords = const_cast<PointerCoords&>( + *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex))); + const auto& [iter, _] = mFilteredPointers.try_emplace(pointerId, mMinCutoffFreq, mBeta); + iter->second.filter(eventTime, pointerCoords); + } } } + +std::chrono::nanoseconds FilteredLegacyResampler::getResampleLatency() const { + return mResampler.getResampleLatency(); +} + } // namespace android diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index e23fc94c5e..31592cd6e3 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -49,6 +49,12 @@ interface IInputConstants const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000; /** + * The key event triggered a key gesture. Used in policy flag to notify that a key gesture was + * triggered using the event. + */ + const int POLICY_FLAG_KEY_GESTURE_TRIGGERED = 0x40000; + + /** * Common input event flag used for both motion and key events for a gesture or pointer being * canceled. */ diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 60fb00e128..fd7704815f 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -94,13 +94,6 @@ flag { } flag { - name: "enable_new_mouse_pointer_ballistics" - namespace: "input" - description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones" - bug: "315313622" -} - -flag { name: "rate_limit_user_activity_poke_in_dispatcher" namespace: "input" description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher." @@ -207,3 +200,34 @@ 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" +} + +flag { + name: "set_input_device_kernel_wake" + namespace: "input" + description: "Set input device's power/wakeup sysfs node" + bug: "372812925" +} + +flag { + name: "enable_alphabetic_keyboard_wake" + namespace: "input" + description: "Enable wake from alphabetic keyboards." + bug: "352856881" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "connected_displays_cursor" + namespace: "lse_desktop_experience" + description: "Allow cursor to transition across multiple connected displays" + bug: "362719483" +} diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs index 6bdcefda36..beb6e23478 100644 --- a/libs/input/rust/data_store.rs +++ b/libs/input/rust/data_store.rs @@ -17,7 +17,7 @@ //! Contains the DataStore, used to store input related data in a persistent way. use crate::input::KeyboardType; -use log::{debug, error}; +use log::{debug, error, info}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{Read, Write}; @@ -157,7 +157,7 @@ impl FileReaderWriter for DefaultFileReaderWriter { let path = Path::new(&self.filepath); let mut fs_string = String::new(); match File::open(path) { - Err(e) => error!("couldn't open {:?}: {}", path, e), + Err(e) => info!("couldn't open {:?}: {}", path, e), Ok(mut file) => match file.read_to_string(&mut fs_string) { Err(e) => error!("Couldn't read from {:?}: {}", path, e), Ok(_) => debug!("Successfully read from file {:?}", path), diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index e04236b9f5..0167c433cb 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -17,6 +17,8 @@ cc_test { "IdGenerator_test.cpp", "InputChannel_test.cpp", "InputConsumer_test.cpp", + "InputConsumerFilteredResampling_test.cpp", + "InputConsumerResampling_test.cpp", "InputDevice_test.cpp", "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", @@ -24,6 +26,7 @@ cc_test { "InputVerifier_test.cpp", "MotionPredictor_test.cpp", "MotionPredictorMetricsManager_test.cpp", + "OneEuroFilter_test.cpp", "Resampler_test.cpp", "RingBuffer_test.cpp", "TestInputChannel.cpp", diff --git a/libs/input/tests/InputConsumerFilteredResampling_test.cpp b/libs/input/tests/InputConsumerFilteredResampling_test.cpp new file mode 100644 index 0000000000..757cd18a38 --- /dev/null +++ b/libs/input/tests/InputConsumerFilteredResampling_test.cpp @@ -0,0 +1,218 @@ +/** + * 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/InputConsumerNoResampling.h> + +#include <chrono> +#include <iostream> +#include <memory> +#include <queue> + +#include <TestEventMatchers.h> +#include <TestInputChannel.h> +#include <android-base/logging.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <input/Input.h> +#include <input/InputEventBuilders.h> +#include <input/Resampler.h> +#include <utils/Looper.h> +#include <utils/StrongPointer.h> + +namespace android { +namespace { + +using std::chrono::nanoseconds; + +using ::testing::AllOf; +using ::testing::Matcher; + +const int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +const int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; + +struct Pointer { + int32_t id{0}; + ToolType toolType{ToolType::FINGER}; + float x{0.0f}; + float y{0.0f}; + bool isResampled{false}; + + PointerBuilder asPointerBuilder() const { + return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled); + } +}; + +} // namespace + +class InputConsumerFilteredResamplingTest : public ::testing::Test, public InputConsumerCallbacks { +protected: + InputConsumerFilteredResamplingTest() + : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")}, + mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} { + Looper::setForThread(mLooper); + mConsumer = std::make_unique< + InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, []() { + return std::make_unique<FilteredLegacyResampler>(/*minCutoffFreq=*/4.7, /*beta=*/0.01); + }); + } + + void invokeLooperCallback() const { + sp<LooperCallback> callback; + ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr, + /*events=*/nullptr, &callback, /*data=*/nullptr)); + ASSERT_NE(callback, nullptr); + callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr); + } + + void assertOnBatchedInputEventPendingWasCalled() { + ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL) + << "onBatchedInputEventPending was not called"; + --mOnBatchedInputEventPendingInvocationCount; + } + + void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) { + ASSERT_TRUE(!mMotionEvents.empty()) << "No motion events were received"; + std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front()); + mMotionEvents.pop(); + ASSERT_NE(motionEvent, nullptr) << "The consumed motion event must not be nullptr"; + EXPECT_THAT(*motionEvent, matcher); + } + + InputMessage nextPointerMessage(nanoseconds eventTime, int32_t action, const Pointer& pointer); + + std::shared_ptr<TestInputChannel> mClientTestChannel; + sp<Looper> mLooper; + std::unique_ptr<InputConsumerNoResampling> mConsumer; + + // Batched input events + std::queue<std::unique_ptr<KeyEvent>> mKeyEvents; + std::queue<std::unique_ptr<MotionEvent>> mMotionEvents; + std::queue<std::unique_ptr<FocusEvent>> mFocusEvents; + std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents; + std::queue<std::unique_ptr<DragEvent>> mDragEvents; + std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents; + +private: + // InputConsumer callbacks + void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override { + mKeyEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override { + mMotionEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + void onBatchedInputEventPending(int32_t pendingBatchSource) override { + if (!mConsumer->probablyHasInput()) { + ADD_FAILURE() << "Should deterministically have input because there is a batch"; + } + ++mOnBatchedInputEventPendingInvocationCount; + } + + void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override { + mFocusEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override { + mCaptureEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override { + mDragEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override { + mTouchModeEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, /*handled=*/true); + } + + uint32_t mLastSeq{0}; + size_t mOnBatchedInputEventPendingInvocationCount{0}; +}; + +InputMessage InputConsumerFilteredResamplingTest::nextPointerMessage(nanoseconds eventTime, + int32_t action, + const Pointer& pointer) { + ++mLastSeq; + return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq} + .eventTime(eventTime.count()) + .source(AINPUT_SOURCE_TOUCHSCREEN) + .action(action) + .pointer(pointer.asPointerBuilder()) + .build(); +} + +TEST_F(InputConsumerFilteredResamplingTest, NeighboringTimestampsDoNotResultInZeroDivision) { + mClientTestChannel->enqueueMessage( + nextPointerMessage(0ms, ACTION_DOWN, Pointer{.x = 0.0f, .y = 0.0f})); + + invokeLooperCallback(); + + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithSampleCount(1))); + + const std::chrono::nanoseconds initialTime{56'821'700'000'000}; + + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 4'929'000ns, ACTION_MOVE, + Pointer{.x = 1.0f, .y = 1.0f})); + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 9'352'000ns, ACTION_MOVE, + Pointer{.x = 2.0f, .y = 2.0f})); + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 14'531'000ns, ACTION_MOVE, + Pointer{.x = 3.0f, .y = 3.0f})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(initialTime.count() + 18'849'395 /*ns*/); + + assertOnBatchedInputEventPendingWasCalled(); + // Three samples are expected. The first two of the batch, and the resampled one. The + // coordinates of the resampled sample are hardcoded because the matcher requires them. However, + // the primary intention here is to check that the last sample is resampled. + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(3), + WithSample(/*sampleIndex=*/2, + Sample{initialTime + 13'849'395ns, + {PointerArgs{.x = 1.3286f, + .y = 1.3286f, + .isResampled = true}}}))); + + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 20'363'000ns, ACTION_MOVE, + Pointer{.x = 4.0f, .y = 4.0f})); + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 25'745'000ns, ACTION_MOVE, + Pointer{.x = 5.0f, .y = 5.0f})); + // This sample is part of the stream of messages, but should not be consumed because its + // timestamp is greater than the ajusted frame time. + mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 31'337'000ns, ACTION_MOVE, + Pointer{.x = 6.0f, .y = 6.0f})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(initialTime.count() + 35'516'062 /*ns*/); + + assertOnBatchedInputEventPendingWasCalled(); + // Four samples are expected because the last sample of the previous batch was not consumed. + assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(4))); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true); +} + +} // namespace android diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp new file mode 100644 index 0000000000..97688a83ae --- /dev/null +++ b/libs/input/tests/InputConsumerResampling_test.cpp @@ -0,0 +1,744 @@ +/* + * Copyright (C) 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/InputConsumerNoResampling.h> + +#include <chrono> +#include <memory> +#include <string> +#include <vector> + +#include <TestEventMatchers.h> +#include <TestInputChannel.h> +#include <attestation/HmacKeyManager.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <input/BlockingQueue.h> +#include <input/InputEventBuilders.h> +#include <input/Resampler.h> +#include <utils/Looper.h> +#include <utils/StrongPointer.h> + +namespace android { +namespace { + +using std::chrono::nanoseconds; +using namespace std::chrono_literals; + +const std::chrono::milliseconds RESAMPLE_LATENCY{5}; + +struct Pointer { + int32_t id{0}; + float x{0.0f}; + float y{0.0f}; + ToolType toolType{ToolType::FINGER}; + bool isResampled{false}; + + PointerBuilder asPointerBuilder() const { + return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled); + } +}; + +struct InputEventEntry { + std::chrono::nanoseconds eventTime{0}; + std::vector<Pointer> pointers{}; + int32_t action{-1}; +}; + +} // namespace + +class InputConsumerResamplingTest : public ::testing::Test, public InputConsumerCallbacks { +protected: + InputConsumerResamplingTest() + : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")}, + mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} { + Looper::setForThread(mLooper); + mConsumer = std::make_unique< + InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, + []() { return std::make_unique<LegacyResampler>(); }); + } + + void invokeLooperCallback() const { + sp<LooperCallback> callback; + ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr, + /*events=*/nullptr, &callback, /*data=*/nullptr)); + ASSERT_NE(callback, nullptr); + callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr); + } + + InputMessage nextPointerMessage(const InputEventEntry& entry); + + void assertReceivedMotionEvent(const std::vector<InputEventEntry>& expectedEntries); + + std::shared_ptr<TestInputChannel> mClientTestChannel; + sp<Looper> mLooper; + std::unique_ptr<InputConsumerNoResampling> mConsumer; + + BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents; + BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents; + BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents; + BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents; + BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents; + BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents; + +private: + uint32_t mLastSeq{0}; + size_t mOnBatchedInputEventPendingInvocationCount{0}; + + // InputConsumerCallbacks interface + void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override { + mKeyEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override { + mMotionEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onBatchedInputEventPending(int32_t pendingBatchSource) override { + if (!mConsumer->probablyHasInput()) { + ADD_FAILURE() << "should deterministically have input because there is a batch"; + } + ++mOnBatchedInputEventPendingInvocationCount; + } + void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override { + mFocusEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override { + mCaptureEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override { + mDragEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override { + mTouchModeEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } +}; + +InputMessage InputConsumerResamplingTest::nextPointerMessage(const InputEventEntry& entry) { + ++mLastSeq; + InputMessageBuilder messageBuilder = InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq} + .eventTime(entry.eventTime.count()) + .deviceId(1) + .action(entry.action) + .downTime(0); + for (const Pointer& pointer : entry.pointers) { + messageBuilder.pointer(pointer.asPointerBuilder()); + } + return messageBuilder.build(); +} + +void InputConsumerResamplingTest::assertReceivedMotionEvent( + const std::vector<InputEventEntry>& expectedEntries) { + std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop(); + ASSERT_NE(motionEvent, nullptr); + + ASSERT_EQ(motionEvent->getHistorySize() + 1, expectedEntries.size()); + + for (size_t sampleIndex = 0; sampleIndex < expectedEntries.size(); ++sampleIndex) { + SCOPED_TRACE("sampleIndex: " + std::to_string(sampleIndex)); + const InputEventEntry& expectedEntry = expectedEntries[sampleIndex]; + EXPECT_EQ(motionEvent->getHistoricalEventTime(sampleIndex), + expectedEntry.eventTime.count()); + EXPECT_EQ(motionEvent->getPointerCount(), expectedEntry.pointers.size()); + EXPECT_EQ(motionEvent->getAction(), expectedEntry.action); + + for (size_t pointerIndex = 0; pointerIndex < expectedEntry.pointers.size(); + ++pointerIndex) { + SCOPED_TRACE("pointerIndex: " + std::to_string(pointerIndex)); + ssize_t eventPointerIndex = + motionEvent->findPointerIndex(expectedEntry.pointers[pointerIndex].id); + EXPECT_EQ(motionEvent->getHistoricalRawX(eventPointerIndex, sampleIndex), + expectedEntry.pointers[pointerIndex].x); + EXPECT_EQ(motionEvent->getHistoricalRawY(eventPointerIndex, sampleIndex), + expectedEntry.pointers[pointerIndex].y); + EXPECT_EQ(motionEvent->getHistoricalX(eventPointerIndex, sampleIndex), + expectedEntry.pointers[pointerIndex].x); + EXPECT_EQ(motionEvent->getHistoricalY(eventPointerIndex, sampleIndex), + expectedEntry.pointers[pointerIndex].y); + EXPECT_EQ(motionEvent->isResampled(pointerIndex, sampleIndex), + expectedEntry.pointers[pointerIndex].isResampled); + } + } +} + +/** + * Timeline + * ---------+------------------+------------------+--------+-----------------+---------------------- + * 0 ms 10 ms 20 ms 25 ms 35 ms + * ACTION_DOWN ACTION_MOVE ACTION_MOVE ^ ^ + * | | + * resampled value | + * frameTime + * Typically, the prediction is made for time frameTime - RESAMPLE_LATENCY, or 30 ms in this case, + * where RESAMPLE_LATENCY equals 5 milliseconds. However, that would be 10 ms later than the last + * real sample (which came in at 20 ms). Therefore, the resampling should happen at 20 ms + + * RESAMPLE_MAX_PREDICTION = 28 ms, where RESAMPLE_MAX_PREDICTION equals 8 milliseconds. In this + * situation, though, resample time is further limited by taking half of the difference between the + * last two real events, which would put this time at: 20 ms + (20 ms - 10 ms) / 2 = 25 ms. + */ +TEST_F(InputConsumerResamplingTest, EventIsResampled) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent( + {InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Same as above test, but use pointer id=1 instead of 0 to make sure that system does not + * have these hardcoded. + */ +TEST_F(InputConsumerResamplingTest, EventIsResampledWithDifferentId) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent( + {InputEventEntry{10ms, + {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 1, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Stylus pointer coordinates are resampled. + */ +TEST_F(InputConsumerResamplingTest, StylusEventIsResampled) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, + .x = 10.0f, + .y = 20.0f, + .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent({InputEventEntry{10ms, + {Pointer{.id = 0, + .x = 20.0f, + .y = 30.0f, + .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, + .x = 30.0f, + .y = 30.0f, + .toolType = ToolType::STYLUS}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 0, + .x = 35.0f, + .y = 30.0f, + .toolType = ToolType::STYLUS, + .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Mouse pointer coordinates are resampled. + */ +TEST_F(InputConsumerResamplingTest, MouseEventIsResampled) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, + .x = 10.0f, + .y = 20.0f, + .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent({InputEventEntry{10ms, + {Pointer{.id = 0, + .x = 20.0f, + .y = 30.0f, + .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, + .x = 30.0f, + .y = 30.0f, + .toolType = ToolType::MOUSE}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 0, + .x = 35.0f, + .y = 30.0f, + .toolType = ToolType::MOUSE, + .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Motion events with palm tool type are not resampled. + */ +TEST_F(InputConsumerResamplingTest, PalmEventIsNotResampled) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent( + {InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent( + {InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Event should not be resampled when sample time is equal to event time. + */ +TEST_F(InputConsumerResamplingTest, SampleTimeEqualsEventTime) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count()); + + // MotionEvent should not resampled because the resample time falls exactly on the existing + // event time. + assertReceivedMotionEvent({InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +/** + * Once we send a resampled value to the app, we should continue to send the last predicted value if + * a pointer does not move. Only real values are used to determine if a pointer does not move. + */ +TEST_F(InputConsumerResamplingTest, ResampledValueIsUsedForIdenticalCoordinates) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent( + {InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + // Coordinate value 30 has been resampled to 35. When a new event comes in with value 30 again, + // the system should still report 35. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {40ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count()); + // Original and resampled event should be both overwritten. + assertReceivedMotionEvent( + {InputEventEntry{40ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{45ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); +} + +TEST_F(InputConsumerResamplingTest, OldEventReceivedAfterResampleOccurs) { + // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an + // InputEvent with a single action. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count()); + assertReceivedMotionEvent( + {InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{25ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + // Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY + // because we are further bound by how far we can extrapolate by the "last time delta". + // That's 50% of (20 ms - 10ms) => 5ms. So we can't predict more than 5 ms into the future + // from the event at 20ms, which is why the resampled event is at t = 25 ms. + + // We resampled the event to 25 ms. Now, an older 'real' event comes in. + mClientTestChannel->enqueueMessage(nextPointerMessage( + {24ms, {Pointer{.id = 0, .x = 40.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{50ms}.count()); + // Original and resampled event should be both overwritten. + assertReceivedMotionEvent( + {InputEventEntry{24ms, + {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{26ms, + {Pointer{.id = 0, .x = 45.0f, .y = 30.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); +} + +TEST_F(InputConsumerResamplingTest, DoNotResampleWhenFrameTimeIsNotAvailable) { + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage(nextPointerMessage( + {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(std::nullopt); + assertReceivedMotionEvent({InputEventEntry{10ms, + {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{20ms, + {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, + AMOTION_EVENT_ACTION_MOVE}}); + + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); +} + +TEST_F(InputConsumerResamplingTest, TwoPointersAreResampledIndependently) { + // Full action for when a pointer with index=1 appears (some other pointer must already be + // present) + const int32_t actionPointer1Down = + AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + + // Full action for when a pointer with index=0 disappears (some other pointer must still remain) + const int32_t actionPointer0Up = + AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + + mClientTestChannel->enqueueMessage(nextPointerMessage( + {0ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_DOWN})); + + mClientTestChannel->assertNoSentMessages(); + + invokeLooperCallback(); + assertReceivedMotionEvent({InputEventEntry{0ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, + AMOTION_EVENT_ACTION_DOWN}}); + + mClientTestChannel->enqueueMessage(nextPointerMessage( + {10ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{10ms + RESAMPLE_LATENCY}.count()); + // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime + assertReceivedMotionEvent({InputEventEntry{10ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, + AMOTION_EVENT_ACTION_MOVE}}); + + // Second pointer id=1 appears + mClientTestChannel->enqueueMessage( + nextPointerMessage({15ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}, + Pointer{.id = 1, .x = 500.0f, .y = 500.0f}}, + actionPointer1Down})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count()); + // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime. + assertReceivedMotionEvent({InputEventEntry{15ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}, + Pointer{.id = 1, .x = 500.0f, .y = 500.0f}}, + actionPointer1Down}}); + + // Both pointers move + mClientTestChannel->enqueueMessage( + nextPointerMessage({30ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}, + Pointer{.id = 1, .x = 500.0f, .y = 500.0f}}, + AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage( + nextPointerMessage({40ms, + {Pointer{.id = 0, .x = 120.0f, .y = 120.0f}, + Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count()); + assertReceivedMotionEvent( + {InputEventEntry{30ms, + {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}, + Pointer{.id = 1, .x = 500.0f, .y = 500.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{40ms, + {Pointer{.id = 0, .x = 120.0f, .y = 120.0f}, + Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{45ms, + {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true}, + Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + // Both pointers move again + mClientTestChannel->enqueueMessage( + nextPointerMessage({60ms, + {Pointer{.id = 0, .x = 120.0f, .y = 120.0f}, + Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + AMOTION_EVENT_ACTION_MOVE})); + mClientTestChannel->enqueueMessage( + nextPointerMessage({70ms, + {Pointer{.id = 0, .x = 130.0f, .y = 130.0f}, + Pointer{.id = 1, .x = 700.0f, .y = 700.0f}}, + AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{75ms + RESAMPLE_LATENCY}.count()); + + /* + * The pointer id 0 at t = 60 should not be equal to 120 because the value was received twice, + * and resampled to 130. Therefore, if we reported 130, then we should continue to report it as + * such. Likewise, with pointer id 1. + */ + + // Not 120 because it matches a previous real event. + assertReceivedMotionEvent( + {InputEventEntry{60ms, + {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true}, + Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{70ms, + {Pointer{.id = 0, .x = 130.0f, .y = 130.0f}, + Pointer{.id = 1, .x = 700.0f, .y = 700.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{75ms, + {Pointer{.id = 0, .x = 135.0f, .y = 135.0f, .isResampled = true}, + Pointer{.id = 1, .x = 750.0f, .y = 750.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); + + // First pointer id=0 leaves the screen + mClientTestChannel->enqueueMessage( + nextPointerMessage({80ms, + {Pointer{.id = 0, .x = 120.0f, .y = 120.0f}, + Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + actionPointer0Up})); + + invokeLooperCallback(); + // Not resampled event for ACTION_POINTER_UP + assertReceivedMotionEvent({InputEventEntry{80ms, + {Pointer{.id = 0, .x = 120.0f, .y = 120.0f}, + Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + actionPointer0Up}}); + + // Remaining pointer id=1 is still present, but doesn't move + mClientTestChannel->enqueueMessage(nextPointerMessage( + {90ms, {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, AMOTION_EVENT_ACTION_MOVE})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(nanoseconds{100ms}.count()); + + /* + * The latest event with ACTION_MOVE was at t = 70 with value = 700. Thus, the resampled value + * is 700 + ((95 - 70)/(90 - 70))*(600 - 700) = 575. + */ + assertReceivedMotionEvent( + {InputEventEntry{90ms, + {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, + AMOTION_EVENT_ACTION_MOVE}, + InputEventEntry{95ms, + {Pointer{.id = 1, .x = 575.0f, .y = 575.0f, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}}); +} + +} // namespace android diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp index d708316236..226b892fdc 100644 --- a/libs/input/tests/InputConsumer_test.cpp +++ b/libs/input/tests/InputConsumer_test.cpp @@ -16,6 +16,9 @@ #include <input/InputConsumerNoResampling.h> +#include <gtest/gtest.h> + +#include <chrono> #include <memory> #include <optional> @@ -25,7 +28,9 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> #include <input/BlockingQueue.h> +#include <input/Input.h> #include <input/InputEventBuilders.h> +#include <input/Resampler.h> #include <utils/Looper.h> #include <utils/StrongPointer.h> @@ -37,8 +42,21 @@ using std::chrono::nanoseconds; using ::testing::AllOf; using ::testing::Matcher; -using ::testing::Not; +constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; + +struct Pointer { + int32_t id{0}; + ToolType toolType{ToolType::FINGER}; + float x{0.0f}; + float y{0.0f}; + bool isResampled{false}; + + PointerBuilder asPointerBuilder() const { + return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled); + } +}; } // namespace class InputConsumerTest : public testing::Test, public InputConsumerCallbacks { @@ -47,16 +65,25 @@ protected: : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")}, mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} { Looper::setForThread(mLooper); - mConsumer = - std::make_unique<InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, - std::make_unique<LegacyResampler>()); + mConsumer = std::make_unique< + InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, + []() { return std::make_unique<LegacyResampler>(); }); } - void invokeLooperCallback() const { + bool invokeLooperCallback() const { sp<LooperCallback> callback; - ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr, - /*events=*/nullptr, &callback, /*data=*/nullptr)); + const bool found = + mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr, + /*events=*/nullptr, &callback, /*data=*/nullptr); + if (!found) { + return false; + } + if (callback == nullptr) { + LOG(FATAL) << "Looper has the fd of interest, but the callback is null!"; + return false; + } callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr); + return true; } void assertOnBatchedInputEventPendingWasCalled() { @@ -65,34 +92,52 @@ protected: --mOnBatchedInputEventPendingInvocationCount; } - void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) { - std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop(); - ASSERT_NE(motionEvent, nullptr); + std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) { + if (mMotionEvents.empty()) { + ADD_FAILURE() << "No motion events received"; + return nullptr; + } + std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front()); + mMotionEvents.pop(); + if (motionEvent == nullptr) { + ADD_FAILURE() << "The consumed motion event should never be null"; + return nullptr; + } EXPECT_THAT(*motionEvent, matcher); + return motionEvent; } + InputMessage nextPointerMessage(std::chrono::nanoseconds eventTime, DeviceId deviceId, + int32_t action, const Pointer& pointer); + std::shared_ptr<TestInputChannel> mClientTestChannel; sp<Looper> mLooper; std::unique_ptr<InputConsumerNoResampling> mConsumer; - BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents; - BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents; - BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents; - BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents; - BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents; - BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents; + std::queue<std::unique_ptr<KeyEvent>> mKeyEvents; + std::queue<std::unique_ptr<MotionEvent>> mMotionEvents; + std::queue<std::unique_ptr<FocusEvent>> mFocusEvents; + std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents; + std::queue<std::unique_ptr<DragEvent>> mDragEvents; + std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents; + + // Whether or not to automatically call "finish" whenever a motion event is received. + bool mShouldFinishMotions{true}; private: + uint32_t mLastSeq{0}; size_t mOnBatchedInputEventPendingInvocationCount{0}; // InputConsumerCallbacks interface void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override { mKeyEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + mConsumer->finishInputEvent(seq, /*handled=*/true); } void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override { mMotionEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + if (mShouldFinishMotions) { + mConsumer->finishInputEvent(seq, /*handled=*/true); + } } void onBatchedInputEventPending(int32_t pendingBatchSource) override { if (!mConsumer->probablyHasInput()) { @@ -102,34 +147,47 @@ private: }; void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override { mFocusEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + mConsumer->finishInputEvent(seq, /*handled=*/true); }; void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override { mCaptureEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + mConsumer->finishInputEvent(seq, /*handled=*/true); }; void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override { mDragEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + mConsumer->finishInputEvent(seq, /*handled=*/true); } void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override { mTouchModeEvents.push(std::move(event)); - mConsumer->finishInputEvent(seq, true); + mConsumer->finishInputEvent(seq, /*handled=*/true); }; }; +InputMessage InputConsumerTest::nextPointerMessage(std::chrono::nanoseconds eventTime, + DeviceId deviceId, int32_t action, + const Pointer& pointer) { + ++mLastSeq; + return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq} + .eventTime(eventTime.count()) + .deviceId(deviceId) + .source(AINPUT_SOURCE_TOUCHSCREEN) + .action(action) + .pointer(pointer.asPointerBuilder()) + .build(); +} + TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) { mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0} .eventTime(nanoseconds{0ms}.count()) - .action(AMOTION_EVENT_ACTION_DOWN) + .action(ACTION_DOWN) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1} .eventTime(nanoseconds{5ms}.count()) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2} .eventTime(nanoseconds{10ms}.count()) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->assertNoSentMessages(); @@ -140,12 +198,12 @@ TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) { mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt); - std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop(); - ASSERT_NE(downMotionEvent, nullptr); + assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN)); - std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop(); + std::unique_ptr<MotionEvent> moveMotionEvent = + assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE)); ASSERT_NE(moveMotionEvent, nullptr); - EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 3UL); + EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 2UL); mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true); mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); @@ -155,19 +213,19 @@ TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) { TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) { mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0} .eventTime(nanoseconds{0ms}.count()) - .action(AMOTION_EVENT_ACTION_DOWN) + .action(ACTION_DOWN) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1} .eventTime(nanoseconds{5ms}.count()) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2} .eventTime(nanoseconds{10ms}.count()) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3} .eventTime(nanoseconds{15ms}.count()) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->assertNoSentMessages(); @@ -178,56 +236,119 @@ TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) { mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/); - std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop(); - ASSERT_NE(downMotionEvent, nullptr); + assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN)); - std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop(); + std::unique_ptr<MotionEvent> moveMotionEvent = + assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE)); ASSERT_NE(moveMotionEvent, nullptr); const size_t numSamples = moveMotionEvent->getHistorySize() + 1; EXPECT_LT(moveMotionEvent->getHistoricalEventTime(numSamples - 2), moveMotionEvent->getEventTime()); - // Consume all remaining events before ending the test. Otherwise, the smart pointer that owns - // consumer is set to null before destroying consumer. This leads to a member function call on a - // null object. - // TODO(b/332613662): Remove this workaround. - mConsumer->consumeBatchedInputEvents(std::nullopt); + mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + // The event with seq=3 remains unconsumed, and therefore finish will not be called for it until + // after the consumer is destroyed. + mConsumer.reset(); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/false); + mClientTestChannel->assertNoSentMessages(); +} + +/** + * During normal operation, the user of InputConsumer (callbacks) is expected to call "finish" + * for each input event received in InputConsumerCallbacks. + * If the InputConsumer is destroyed, the events that were already sent to the callbacks will not + * be finished automatically. + */ +TEST_F(InputConsumerTest, UnhandledEventsNotFinishedInDestructor) { + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build()); + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build()); + mShouldFinishMotions = false; + invokeLooperCallback(); + assertOnBatchedInputEventPendingWasCalled(); + assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN)); + mClientTestChannel->assertNoSentMessages(); + // The "finishInputEvent" was not called by the InputConsumerCallbacks. + // Now, destroy the consumer and check that the "finish" was not called automatically for the + // DOWN event, but was called for the undelivered MOVE event. + mConsumer.reset(); + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false); + mClientTestChannel->assertNoSentMessages(); +} - mClientTestChannel->assertFinishMessage(/*seq=*/0, true); - mClientTestChannel->assertFinishMessage(/*seq=*/1, true); - mClientTestChannel->assertFinishMessage(/*seq=*/2, true); - mClientTestChannel->assertFinishMessage(/*seq=*/3, true); +/** + * Check what happens when looper invokes callback after consumer has been destroyed. + * This reproduces a crash where the LooperEventCallback was added back to the Looper during + * destructor, thus allowing the looper callback to be invoked onto a null consumer object. + */ +TEST_F(InputConsumerTest, LooperCallbackInvokedAfterConsumerDestroyed) { + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build()); + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build()); + ASSERT_TRUE(invokeLooperCallback()); + assertOnBatchedInputEventPendingWasCalled(); + assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN)); + mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true); + + // Now, destroy the consumer and invoke the looper callback again after it's been destroyed. + mConsumer.reset(); + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false); + ASSERT_FALSE(invokeLooperCallback()); +} + +/** + * Send an event to the InputConsumer, but do not invoke "consumeBatchedInputEvents", thus leaving + * the input event unconsumed by the callbacks. Ensure that no crash occurs when the consumer is + * destroyed. + * This test is similar to the one above, but here we are calling "finish" + * automatically for any event received in the callbacks. + */ +TEST_F(InputConsumerTest, UnconsumedEventDoesNotCauseACrash) { + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build()); + invokeLooperCallback(); + assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN)); + mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true); + mClientTestChannel->enqueueMessage( + InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build()); + invokeLooperCallback(); + mConsumer.reset(); + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false); } TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) { mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0} .deviceId(0) - .action(AMOTION_EVENT_ACTION_DOWN) + .action(ACTION_DOWN) .build()); invokeLooperCallback(); - assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(ACTION_DOWN))); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1} .deviceId(0) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2} .deviceId(0) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3} .deviceId(0) - .action(AMOTION_EVENT_ACTION_MOVE) + .action(ACTION_MOVE) .build()); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/4} .deviceId(1) - .action(AMOTION_EVENT_ACTION_DOWN) + .action(ACTION_DOWN) .build()); invokeLooperCallback(); - assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(ACTION_DOWN))); mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/5} .deviceId(0) @@ -235,8 +356,7 @@ TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) { .build()); invokeLooperCallback(); - assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - Not(MotionEventIsResampled()))); + assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE))); mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true); mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); @@ -244,4 +364,114 @@ TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) { mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); } + +/** + * The test supposes a 60Hz Vsync rate and a 200Hz input rate. The InputMessages are intertwined as + * in a real use cases. The test's two devices should be resampled independently. Moreover, the + * InputMessage stream layout for the test is: + * + * DOWN(0, 0ms) + * MOVE(0, 5ms) + * MOVE(0, 10ms) + * DOWN(1, 15ms) + * + * CONSUME(16ms) + * + * MOVE(1, 20ms) + * MOVE(1, 25ms) + * MOVE(0, 30ms) + * + * CONSUME(32ms) + * + * MOVE(0, 35ms) + * UP(1, 40ms) + * UP(0, 45ms) + * + * CONSUME(48ms) + * + * The first field is device ID, and the second field is event time. + */ +TEST_F(InputConsumerTest, MultiDeviceResampling) { + mClientTestChannel->enqueueMessage( + nextPointerMessage(0ms, /*deviceId=*/0, ACTION_DOWN, Pointer{.x = 0, .y = 0})); + + mClientTestChannel->assertNoSentMessages(); + + invokeLooperCallback(); + assertReceivedMotionEvent( + AllOf(WithDeviceId(0), WithMotionAction(ACTION_DOWN), WithSampleCount(1))); + + mClientTestChannel->enqueueMessage( + nextPointerMessage(5ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 1.0f, .y = 2.0f})); + mClientTestChannel->enqueueMessage( + nextPointerMessage(10ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 2.0f, .y = 4.0f})); + mClientTestChannel->enqueueMessage( + nextPointerMessage(15ms, /*deviceId=*/1, ACTION_DOWN, Pointer{.x = 10.0f, .y = 10.0f})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/); + + assertReceivedMotionEvent( + AllOf(WithDeviceId(1), WithMotionAction(ACTION_DOWN), WithSampleCount(1))); + assertReceivedMotionEvent( + AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE), WithSampleCount(3), + WithSample(/*sampleIndex=*/2, + Sample{11ms, + {PointerArgs{.x = 2.2f, .y = 4.4f, .isResampled = true}}}))); + + mClientTestChannel->enqueueMessage( + nextPointerMessage(20ms, /*deviceId=*/1, ACTION_MOVE, Pointer{.x = 11.0f, .y = 12.0f})); + mClientTestChannel->enqueueMessage( + nextPointerMessage(25ms, /*deviceId=*/1, ACTION_MOVE, Pointer{.x = 12.0f, .y = 14.0f})); + mClientTestChannel->enqueueMessage( + nextPointerMessage(30ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 5.0f, .y = 6.0f})); + + invokeLooperCallback(); + assertOnBatchedInputEventPendingWasCalled(); + mConsumer->consumeBatchedInputEvents(32'000'000 /*ns*/); + + assertReceivedMotionEvent( + AllOf(WithDeviceId(1), WithMotionAction(ACTION_MOVE), WithSampleCount(3), + WithSample(/*sampleIndex=*/2, + Sample{27ms, + {PointerArgs{.x = 12.4f, .y = 14.8f, .isResampled = true}}}))); + + mClientTestChannel->enqueueMessage( + nextPointerMessage(35ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 8.0f, .y = 9.0f})); + mClientTestChannel->enqueueMessage(nextPointerMessage(40ms, /*deviceId=*/1, + AMOTION_EVENT_ACTION_UP, + Pointer{.x = 12.0f, .y = 14.0f})); + mClientTestChannel->enqueueMessage(nextPointerMessage(45ms, /*deviceId=*/0, + AMOTION_EVENT_ACTION_UP, + Pointer{.x = 8.0f, .y = 9.0f})); + + invokeLooperCallback(); + mConsumer->consumeBatchedInputEvents(48'000'000 /*ns*/); + + assertReceivedMotionEvent( + AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1))); + + assertReceivedMotionEvent( + AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE), WithSampleCount(3), + WithSample(/*sampleIndex=*/2, + Sample{37'500'000ns, + {PointerArgs{.x = 9.5f, .y = 10.5f, .isResampled = true}}}))); + + assertReceivedMotionEvent( + AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1))); + + // The sequence order is based on the expected consumption. Each sequence number corresponds to + // one of the previously enqueued messages. + mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/9, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/7, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/8, /*handled=*/true); + mClientTestChannel->assertFinishMessage(/*seq=*/10, /*handled=*/true); +} + } // namespace android diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp index 1210f711de..1dadae98e4 100644 --- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -14,15 +14,18 @@ * limitations under the License. */ +#include <TestEventMatchers.h> #include <android-base/logging.h> #include <attestation/HmacKeyManager.h> #include <ftl/enum.h> +#include <gmock/gmock.h> #include <gtest/gtest.h> #include <input/BlockingQueue.h> #include <input/InputConsumerNoResampling.h> #include <input/InputTransport.h> using android::base::Result; +using ::testing::Matcher; namespace android { @@ -278,7 +281,7 @@ protected: void SetUp() override { std::unique_ptr<InputChannel> serverChannel; status_t result = - InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel); + InputChannel::openInputChannelPair("test channel", serverChannel, mClientChannel); ASSERT_EQ(OK, result); mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel)); @@ -316,6 +319,8 @@ protected: protected: // Interaction with the looper thread + void blockLooper(); + void unblockLooper(); enum class LooperMessage : int { CALL_PROBABLY_HAS_INPUT, CREATE_CONSUMER, @@ -336,6 +341,8 @@ protected: // accessed on the test thread. BlockingQueue<bool> mProbablyHasInputResponses; + std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher); + private: sp<MessageHandler> mMessageHandler; void handleMessage(const Message& message); @@ -384,11 +391,45 @@ private: }; }; +void InputPublisherAndConsumerNoResamplingTest::blockLooper() { + { + std::scoped_lock l(mLock); + mLooperMayProceed = false; + } + sendMessage(LooperMessage::BLOCK_LOOPER); + { + std::unique_lock l(mLock); + mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; }); + } +} + +void InputPublisherAndConsumerNoResamplingTest::unblockLooper() { + { + std::scoped_lock l(mLock); + mLooperMayProceed = true; + } + mNotifyLooperMayProceed.notify_all(); +} + void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) { Message msg{ftl::to_underlying(message)}; mLooper->sendMessage(mMessageHandler, msg); } +std::unique_ptr<MotionEvent> InputPublisherAndConsumerNoResamplingTest::assertReceivedMotionEvent( + const Matcher<MotionEvent>& matcher) { + std::optional<std::unique_ptr<MotionEvent>> event = mMotionEvents.popWithTimeout(TIMEOUT); + if (!event) { + ADD_FAILURE() << "No event was received, but expected motion " << matcher; + return nullptr; + } + if (*event == nullptr) { + LOG(FATAL) << "Event was received, but it was null"; + } + EXPECT_THAT(**event, matcher); + return std::move(*event); +} + void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) { switch (static_cast<LooperMessage>(message.what)) { case LooperMessage::CALL_PROBABLY_HAS_INPUT: { @@ -572,8 +613,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC); publishMotionEvent(*mPublisher, argsDown); - // Consume the DOWN event. - ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value()); + assertReceivedMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown); @@ -582,15 +622,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu std::queue<uint32_t> publishedSequenceNumbers; // Block Looper to increase the chance of batching events - { - std::scoped_lock l(mLock); - mLooperMayProceed = false; - } - sendMessage(LooperMessage::BLOCK_LOOPER); - { - std::unique_lock l(mLock); - mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; }); - } + blockLooper(); uint32_t firstSampleId; for (size_t i = 0; i < nSamples; ++i) { @@ -611,21 +643,16 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu std::vector<MotionEvent> singleSampledMotionEvents; - // Unblock Looper - { - std::scoped_lock l(mLock); - mLooperMayProceed = true; - } - mNotifyLooperMayProceed.notify_all(); + unblockLooper(); // We have no control over the socket behavior, so the consumer can receive // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a // mix of those) while (singleSampledMotionEvents.size() != nSamples) { - const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent = - mMotionEvents.popWithTimeout(TIMEOUT); + const std::unique_ptr<MotionEvent> batchedMotionEvent = + assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE)); // The events received by these calls are never null - std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent); + std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(*batchedMotionEvent); singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(), splitMotionEvents.end()); } @@ -681,10 +708,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMo } mNotifyLooperMayProceed.notify_all(); - std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT); - ASSERT_TRUE(optMotion.has_value()); - std::unique_ptr<MotionEvent> motion = std::move(*optMotion); - ASSERT_EQ(ACTION_MOVE, motion->getAction()); + assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE)); verifyFinishedSignal(*mPublisher, seq, publishTime); } @@ -696,9 +720,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent( nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); publishMotionEvent(*mPublisher, args); - std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT); - ASSERT_TRUE(optMotion.has_value()); - std::unique_ptr<MotionEvent> event = std::move(*optMotion); + std::unique_ptr<MotionEvent> event = assertReceivedMotionEvent(WithMotionAction(action)); verifyArgsEqualToEvent(args, *event); @@ -796,6 +818,15 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent( verifyFinishedSignal(*mPublisher, seq, publishTime); } +/** + * If the publisher has died, consumer should not crash when trying to send an outgoing message. + */ +TEST_F(InputPublisherAndConsumerNoResamplingTest, ConsumerWritesAfterPublisherDies) { + mPublisher.reset(); // The publisher has died + mReportTimelineArgs.emplace(/*inputEventId=*/10, /*gpuCompletedTime=*/20, /*presentTime=*/30); + sendMessage(LooperMessage::CALL_REPORT_TIMELINE); +} + TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) { const int32_t inputEventId = 20; const nsecs_t gpuCompletedTime = 30; diff --git a/libs/input/tests/OneEuroFilter_test.cpp b/libs/input/tests/OneEuroFilter_test.cpp new file mode 100644 index 0000000000..8645508ea7 --- /dev/null +++ b/libs/input/tests/OneEuroFilter_test.cpp @@ -0,0 +1,137 @@ +/** + * 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/OneEuroFilter.h> + +#include <algorithm> +#include <chrono> +#include <cmath> +#include <numeric> +#include <vector> + +#include <gtest/gtest.h> + +#include <input/Input.h> + +namespace android { +namespace { + +using namespace std::literals::chrono_literals; +using std::chrono::duration; + +struct Sample { + duration<double> timestamp{}; + double value{}; + + friend bool operator<(const Sample& lhs, const Sample& rhs) { return lhs.value < rhs.value; } +}; + +/** + * Generates a sinusoidal signal with the passed frequency and amplitude. + */ +std::vector<Sample> generateSinusoidalSignal(duration<double> signalDuration, + double samplingFrequency, double signalFrequency, + double amplitude) { + std::vector<Sample> signal; + const duration<double> samplingPeriod{1.0 / samplingFrequency}; + for (duration<double> timestamp{0.0}; timestamp < signalDuration; timestamp += samplingPeriod) { + signal.push_back( + Sample{timestamp, + amplitude * std::sin(2.0 * M_PI * signalFrequency * timestamp.count())}); + } + return signal; +} + +double meanAbsoluteError(const std::vector<Sample>& filteredSignal, + const std::vector<Sample>& signal) { + if (filteredSignal.size() != signal.size()) { + ADD_FAILURE() << "filteredSignal and signal do not have equal number of samples"; + return std::numeric_limits<double>::max(); + } + std::vector<double> absoluteError; + for (size_t sampleIndex = 0; sampleIndex < signal.size(); ++sampleIndex) { + absoluteError.push_back( + std::abs(filteredSignal[sampleIndex].value - signal[sampleIndex].value)); + } + if (absoluteError.empty()) { + ADD_FAILURE() << "Zero division. absoluteError is empty"; + return std::numeric_limits<double>::max(); + } + return std::accumulate(absoluteError.begin(), absoluteError.end(), 0.0) / absoluteError.size(); +} + +double maxAbsoluteAmplitude(const std::vector<Sample>& signal) { + if (signal.empty()) { + ADD_FAILURE() << "Max absolute value amplitude does not exist. Signal is empty"; + return std::numeric_limits<double>::max(); + } + std::vector<Sample> absoluteSignal; + for (const Sample& sample : signal) { + absoluteSignal.push_back(Sample{sample.timestamp, std::abs(sample.value)}); + } + return std::max_element(absoluteSignal.begin(), absoluteSignal.end())->value; +} + +} // namespace + +class OneEuroFilterTest : public ::testing::Test { +protected: + // The constructor's parameters are the ones that Chromium's using. The tuning was based on a 60 + // Hz sampling frequency. Refer to their one_euro_filter.h header for additional information + // about these parameters. + OneEuroFilterTest() : mFilter{/*minCutoffFreq=*/4.7, /*beta=*/0.01} {} + + std::vector<Sample> filterSignal(const std::vector<Sample>& signal) { + std::vector<Sample> filteredSignal; + for (const Sample& sample : signal) { + filteredSignal.push_back( + Sample{sample.timestamp, + mFilter.filter(std::chrono::duration_cast<std::chrono::nanoseconds>( + sample.timestamp), + sample.value)}); + } + return filteredSignal; + } + + OneEuroFilter mFilter; +}; + +TEST_F(OneEuroFilterTest, PassLowFrequencySignal) { + const std::vector<Sample> signal = + generateSinusoidalSignal(1s, /*samplingFrequency=*/60, /*signalFrequency=*/1, + /*amplitude=*/1); + + const std::vector<Sample> filteredSignal = filterSignal(signal); + + // The reason behind using the mean absolute error as a metric is that, ideally, a low frequency + // filtered signal is expected to be almost identical to the raw one. Therefore, the error + // between them should be minimal. The constant is heuristically chosen. + EXPECT_LT(meanAbsoluteError(filteredSignal, signal), 0.25); +} + +TEST_F(OneEuroFilterTest, RejectHighFrequencySignal) { + const std::vector<Sample> signal = + generateSinusoidalSignal(1s, /*samplingFrequency=*/60, /*signalFrequency=*/22.5, + /*amplitude=*/1); + + const std::vector<Sample> filteredSignal = filterSignal(signal); + + // The filtered signal should consist of values that are much closer to zero. The comparison + // constant is heuristically chosen. + EXPECT_LT(maxAbsoluteAmplitude(filteredSignal), 0.25); +} + +} // namespace android diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp index 26dee393c1..3162b77c85 100644 --- a/libs/input/tests/Resampler_test.cpp +++ b/libs/input/tests/Resampler_test.cpp @@ -87,7 +87,6 @@ InputSample::operator InputMessage() const { struct InputStream { std::vector<InputSample> samples{}; int32_t action{0}; - DeviceId deviceId{0}; /** * Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with * the relevant data for tests. @@ -100,8 +99,8 @@ InputStream::operator MotionEvent() const { MotionEventBuilder motionEventBuilder = MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER) .downTime(0) - .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count()) - .deviceId(deviceId); + .eventTime( + static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count()); for (const Pointer& pointer : firstSample.pointers) { const PointerBuilder pointerBuilder = PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y); @@ -289,28 +288,6 @@ TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) { assertMotionEventIsNotResampled(originalMotionEvent, motionEvent); } -TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) { - MotionEvent motionFromFirstDevice = - InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}, - InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}}, - AMOTION_EVENT_ACTION_MOVE, - .deviceId = 0}; - - mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr); - - MotionEvent motionFromSecondDevice = - InputStream{{InputSample{11ms, - {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}}, - AMOTION_EVENT_ACTION_MOVE, - .deviceId = 1}; - const MotionEvent originalMotionEvent = motionFromSecondDevice; - - mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr); - // The MotionEvent should not be resampled because the second event came from a different device - // than the previous event. - assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice); -} - TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) { MotionEvent motionEvent = InputStream{{InputSample{10ms, @@ -671,7 +648,15 @@ TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderInterpolation) { mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample); - assertMotionEventIsNotResampled(originalMotionEvent, motionEvent); + assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent, + {Pointer{.id = 0, + .x = 1.4f, + .y = 1.4f, + .isResampled = true}, + Pointer{.id = 1, + .x = 2.4f, + .y = 2.4f, + .isResampled = true}}); } TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) { @@ -693,7 +678,15 @@ TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) { mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr); - assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent); + assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent, + {Pointer{.id = 1, + .x = 4.4f, + .y = 4.4f, + .isResampled = true}, + Pointer{.id = 0, + .x = 3.4f, + .y = 3.4f, + .isResampled = true}}); } TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) { diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h index dd2e40c025..56eaefd074 100644 --- a/libs/input/tests/TestEventMatchers.h +++ b/libs/input/tests/TestEventMatchers.h @@ -16,18 +16,40 @@ #pragma once +#include <chrono> +#include <cmath> #include <ostream> +#include <vector> +#include <android-base/logging.h> +#include <gtest/gtest.h> #include <input/Input.h> namespace android { +namespace { + +using ::testing::Matcher; + +} // namespace + /** * This file contains a copy of Matchers from .../inputflinger/tests/TestEventMatchers.h. Ideally, * implementations must not be duplicated. * TODO(b/365606513): Find a way to share TestEventMatchers.h between inputflinger and libinput. */ +struct PointerArgs { + float x{0.0f}; + float y{0.0f}; + bool isResampled{false}; +}; + +struct Sample { + std::chrono::nanoseconds eventTime{0}; + std::vector<PointerArgs> pointers{}; +}; + class WithDeviceIdMatcher { public: using is_gtest_matcher = void; @@ -54,12 +76,18 @@ public: using is_gtest_matcher = void; explicit WithMotionActionMatcher(int32_t action) : mAction(action) {} - bool MatchAndExplain(const MotionEvent& event, std::ostream*) const { - bool matches = mAction == event.getAction(); - if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) { - matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0; + bool MatchAndExplain(const MotionEvent& event, testing::MatchResultListener* listener) const { + if (mAction != event.getAction()) { + *listener << "expected " << MotionEvent::actionToString(mAction) << ", but got " + << MotionEvent::actionToString(event.getAction()); + return false; + } + if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL && + (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) == 0) { + *listener << "event with CANCEL action is missing FLAG_CANCELED"; + return false; } - return matches; + return true; } void DescribeTo(std::ostream* os) const { @@ -79,32 +107,92 @@ inline WithMotionActionMatcher WithMotionAction(int32_t action) { return WithMotionActionMatcher(action); } -class MotionEventIsResampledMatcher { +class WithSampleCountMatcher { public: using is_gtest_matcher = void; + explicit WithSampleCountMatcher(size_t sampleCount) : mExpectedSampleCount{sampleCount} {} bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream*) const { - const size_t numSamples = motionEvent.getHistorySize() + 1; - const size_t numPointers = motionEvent.getPointerCount(); - if (numPointers <= 0 || numSamples <= 0) { + return (motionEvent.getHistorySize() + 1) == mExpectedSampleCount; + } + + void DescribeTo(std::ostream* os) const { *os << "sample count " << mExpectedSampleCount; } + + void DescribeNegationTo(std::ostream* os) const { *os << "different sample count"; } + +private: + const size_t mExpectedSampleCount; +}; + +inline WithSampleCountMatcher WithSampleCount(size_t sampleCount) { + return WithSampleCountMatcher(sampleCount); +} + +class WithSampleMatcher { +public: + using is_gtest_matcher = void; + explicit WithSampleMatcher(size_t sampleIndex, const Sample& sample) + : mSampleIndex{sampleIndex}, mSample{sample} {} + + bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream* os) const { + if (motionEvent.getHistorySize() < mSampleIndex) { + *os << "sample index out of bounds"; + return false; + } + + if (motionEvent.getHistoricalEventTime(mSampleIndex) != mSample.eventTime.count()) { + *os << "event time mismatch. sample: " + << motionEvent.getHistoricalEventTime(mSampleIndex) + << " expected: " << mSample.eventTime.count(); + return false; + } + + if (motionEvent.getPointerCount() != mSample.pointers.size()) { + *os << "pointer count mismatch. sample: " << motionEvent.getPointerCount() + << " expected: " << mSample.pointers.size(); return false; } - for (size_t i = 0; i < numPointers; ++i) { + + for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); + ++pointerIndex) { const PointerCoords& pointerCoords = - motionEvent.getSamplePointerCoords()[numSamples * numPointers + i]; - if (!pointerCoords.isResampled) { + *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, mSampleIndex)); + + if ((std::abs(pointerCoords.getX() - mSample.pointers[pointerIndex].x) > + MotionEvent::ROUNDING_PRECISION) || + (std::abs(pointerCoords.getY() - mSample.pointers[pointerIndex].y) > + MotionEvent::ROUNDING_PRECISION)) { + *os << "sample coordinates mismatch at pointer index " << pointerIndex + << ". sample: (" << pointerCoords.getX() << ", " << pointerCoords.getY() + << ") expected: (" << mSample.pointers[pointerIndex].x << ", " + << mSample.pointers[pointerIndex].y << ")"; + return false; + } + + if (motionEvent.isResampled(pointerIndex, mSampleIndex) != + mSample.pointers[pointerIndex].isResampled) { + *os << "resampling flag mismatch. sample: " + << motionEvent.isResampled(pointerIndex, mSampleIndex) + << " expected: " << mSample.pointers[pointerIndex].isResampled; return false; } } return true; } - void DescribeTo(std::ostream* os) const { *os << "MotionEvent is resampled."; } + void DescribeTo(std::ostream* os) const { *os << "motion event sample properties match."; } - void DescribeNegationTo(std::ostream* os) const { *os << "MotionEvent is not resampled."; } + void DescribeNegationTo(std::ostream* os) const { + *os << "motion event sample properties do not match expected properties."; + } + +private: + const size_t mSampleIndex; + const Sample mSample; }; -inline MotionEventIsResampledMatcher MotionEventIsResampled() { - return MotionEventIsResampledMatcher(); +inline WithSampleMatcher WithSample(size_t sampleIndex, const Sample& sample) { + return WithSampleMatcher(sampleIndex, sample); } + } // namespace android diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp index c3ac0b7cfb..0c19ebe651 100644 --- a/libs/input/tests/TfLiteMotionPredictor_test.cpp +++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp @@ -89,23 +89,23 @@ TEST(TfLiteMotionPredictorTest, BuffersCopyTo) { buffers.pushSample(/*timestamp=*/1, {.position = {.x = 10, .y = 10}, .pressure = 0, - .orientation = 0, - .tilt = 0.2}); + .tilt = 0.2, + .orientation = 0}); buffers.pushSample(/*timestamp=*/2, {.position = {.x = 10, .y = 50}, .pressure = 0.4, - .orientation = M_PI / 4, - .tilt = 0.3}); + .tilt = 0.3, + .orientation = M_PI / 4}); buffers.pushSample(/*timestamp=*/3, {.position = {.x = 30, .y = 50}, .pressure = 0.5, - .orientation = -M_PI / 4, - .tilt = 0.4}); + .tilt = 0.4, + .orientation = -M_PI / 4}); buffers.pushSample(/*timestamp=*/3, {.position = {.x = 30, .y = 60}, .pressure = 0, - .orientation = 0, - .tilt = 0.5}); + .tilt = 0.5, + .orientation = 0}); buffers.copyTo(*model); const int zeroPadding = model->inputLength() - 3; diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 8d8b5300c1..9841c03826 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -571,11 +571,12 @@ TEST_F(TouchResamplingTest, TwoPointersAreResampledIndependently) { std::chrono::nanoseconds frameTime; std::vector<InputEventEntry> entries, expectedEntries; - // full action for when a pointer with id=1 appears (some other pointer must already be present) + // full action for when a pointer with index=1 appears (some other pointer must already be + // present) constexpr int32_t actionPointer1Down = AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - // full action for when a pointer with id=0 disappears (some other pointer must still remain) + // full action for when a pointer with index=0 disappears (some other pointer must still remain) constexpr int32_t actionPointer0Up = AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h index f1453bd64d..006a785cb7 100644 --- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h +++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h @@ -344,7 +344,8 @@ protected: * mEglSlots array in addition to the ConsumerBase. */ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer, - EGLDisplay display, EGLSyncKHR eglFence) override; + EGLDisplay display = EGL_NO_DISPLAY, + EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override; /** * freeBufferLocked frees up the given buffer slot. If the slot has been diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp index 275b7a4888..3959fce008 100644 --- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp +++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp @@ -150,8 +150,7 @@ status_t EGLConsumer::releaseTexImage(SurfaceTexture& st) { } } - err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer, mEglDisplay, - EGL_NO_SYNC_KHR); + err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer); if (err < NO_ERROR) { EGC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err); return err; @@ -234,14 +233,14 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele if (st.mOpMode != SurfaceTexture::OpMode::attachedToGL) { EGC_LOGE("updateAndRelease: EGLConsumer is not attached to an OpenGL " "ES context"); - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return INVALID_OPERATION; } // Confirm state. err = checkAndUpdateEglStateLocked(st); if (err != NO_ERROR) { - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return err; } @@ -254,7 +253,7 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele if (err != NO_ERROR) { EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay, slot); - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return UNKNOWN_ERROR; } @@ -266,8 +265,7 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele // release the old buffer, so instead we just drop the new frame. // As we are still under lock since acquireBuffer, it is safe to // release by slot. - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, - EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return err; } } diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp index 32b229d77c..60e87b54d5 100644 --- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp +++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp @@ -64,8 +64,7 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace // Wait on the producer fence for the buffer to be ready. err = fenceWait(item.mFence->get(), fencePassThroughHandle); if (err != OK) { - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return nullptr; } } @@ -79,8 +78,7 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace err = createFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(), &display, &releaseFenceId, fencePassThroughHandle); if (OK != err) { - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return nullptr; } @@ -91,8 +89,7 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace releaseFence); if (err != OK) { IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err); - st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, - EGL_NO_SYNC_KHR); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer); return nullptr; } } diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index f97eed5db3..f5a1db546d 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -222,6 +222,8 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa static_cast<int>(HAL_DATASPACE_BT2020_ITU_HLG)); static_assert(static_cast<int>(ADATASPACE_DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH)); static_assert(static_cast<int>(ADATASPACE_DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH)); + static_assert(static_cast<int>(ADATASPACE_DISPLAY_BT2020) == + static_cast<int>(HAL_DATASPACE_DISPLAY_BT2020)); if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { return -EINVAL; diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h index 8056d9ac4f..295a307c3c 100644 --- a/libs/nativewindow/include/android/data_space.h +++ b/libs/nativewindow/include/android/data_space.h @@ -578,6 +578,13 @@ enum ADataSpace : int32_t { */ ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG | // ADATASPACE_RANGE_LIMITED + /** + * sRGB-encoded BT. 2020 + * + * Uses full range, sRGB transfer and BT2020 standard. + */ + ADATASPACE_DISPLAY_BT2020 = 142999552, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SRGB + // | ADATASPACE_RANGE_FULL /** * Depth diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index 6f816bf614..ed3e8c1a62 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -243,8 +243,7 @@ enum ANativeWindow_FrameRateCompatibility { * There are no inherent restrictions on the frame rate of this window. When * the system selects a frame rate other than what the app requested, the * app will be able to run at the system frame rate without requiring pull - * down. This value should be used when displaying game content, UIs, and - * anything that isn't video. + * down. This value should be used when displaying game content. */ ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0, /** @@ -256,7 +255,14 @@ enum ANativeWindow_FrameRateCompatibility { * stuttering) than it would be if the system had chosen the app's requested * frame rate. This value should be used for video content. */ - ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1 + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1, + + /** + * The window requests a frame rate that is greater than or equal to the specified frame rate. + * This value should be used for UIs, animations, scrolling, and anything that is not a game + * or video. + */ + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE = 2 }; /** diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index 33c303ae71..f669f7781e 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -1060,12 +1060,7 @@ enum { /** * This surface will vote for the minimum refresh rate. */ - ANATIVEWINDOW_FRAME_RATE_MIN, - - /** - * The surface requests a frame rate that is greater than or equal to `frameRate`. - */ - ANATIVEWINDOW_FRAME_RATE_GTE + ANATIVEWINDOW_FRAME_RATE_MIN }; /* diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index d248ea0b84..7f207f0670 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -105,6 +105,7 @@ filegroup { "skia/filters/KawaseBlurDualFilter.cpp", "skia/filters/KawaseBlurFilter.cpp", "skia/filters/LinearEffect.cpp", + "skia/filters/LutShader.cpp", "skia/filters/MouriMap.cpp", "skia/filters/StretchShaderFactory.cpp", "skia/filters/EdgeExtensionShaderFactory.cpp", diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index a9264b3914..595573d24a 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -17,6 +17,7 @@ #include <RenderEngineBench.h> #include <android-base/file.h> #include <benchmark/benchmark.h> +#include <com_android_graphics_libgui_flags.h> #include <gui/SurfaceComposerClient.h> #include <log/log.h> #include <renderengine/ExternalTexture.h> @@ -321,5 +322,7 @@ BENCHMARK_CAPTURE(BM_homescreen_blur, kawase_dual_filter, RenderEngine::Threaded BENCHMARK_CAPTURE(BM_homescreen, SkiaGLThreaded, RenderEngine::Threaded::YES, RenderEngine::GraphicsApi::GL); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_EDGE_EXTENSION_SHADER BENCHMARK_CAPTURE(BM_homescreen_edgeExtension, SkiaGLThreaded, RenderEngine::Threaded::YES, RenderEngine::GraphicsApi::GL); +#endif diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h index 859ae8b6e2..ac43da8dcf 100644 --- a/libs/renderengine/include/renderengine/LayerSettings.h +++ b/libs/renderengine/include/renderengine/LayerSettings.h @@ -16,6 +16,7 @@ #pragma once +#include <gui/DisplayLuts.h> #include <math/mat4.h> #include <math/vec3.h> #include <renderengine/ExternalTexture.h> @@ -145,6 +146,8 @@ struct LayerSettings { // If white point nits are unknown, then this layer is assumed to have the // same luminance as the brightest layer in the scene. float whitePointNits = -1.f; + + std::shared_ptr<gui::DisplayLuts> luts; }; // Keep in sync with custom comparison function in @@ -187,7 +190,7 @@ static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs lhs.blurRegionTransform == rhs.blurRegionTransform && lhs.stretchEffect == rhs.stretchEffect && lhs.edgeExtensionEffect == rhs.edgeExtensionEffect && - lhs.whitePointNits == rhs.whitePointNits; + lhs.whitePointNits == rhs.whitePointNits && lhs.luts == rhs.luts; } static inline void PrintTo(const Buffer& settings, ::std::ostream* os) { diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 0fd982e812..95c4d033e2 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -38,6 +38,14 @@ #define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend" /** + * Allows opting particular devices into an initial preview rollout of RenderEngine on Graphite. + * + * Only applicable within SurfaceFlinger, and if relevant aconfig flags are enabled. + */ +#define PROPERTY_DEBUG_RENDERENGINE_GRAPHITE_PREVIEW_OPTIN \ + "debug.renderengine.graphite_preview_optin" + +/** * Turns on recording of skia commands in SkiaGL version of the RE. This property * defines number of milliseconds for the recording to take place. A non zero value * turns on the recording. diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp index a3a43e20be..cc73f405a6 100644 --- a/libs/renderengine/skia/GaneshVkRenderEngine.cpp +++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp @@ -21,12 +21,15 @@ #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h> +#include <android-base/stringprintf.h> #include <common/trace.h> #include <log/log_main.h> #include <sync/sync.h> namespace android::renderengine::skia { +using base::StringAppendF; + std::unique_ptr<GaneshVkRenderEngine> GaneshVkRenderEngine::create( const RenderEngineCreationArgs& args) { std::unique_ptr<GaneshVkRenderEngine> engine(new GaneshVkRenderEngine(args)); @@ -111,4 +114,9 @@ base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context, return res; } +void GaneshVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { + StringAppendF(&result, "\n ------------RE Vulkan (Ganesh)----------\n"); + SkiaVkRenderEngine::appendBackendSpecificInfoToDump(result); +} + } // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h index e6123c21bf..ba17f71201 100644 --- a/libs/renderengine/skia/GaneshVkRenderEngine.h +++ b/libs/renderengine/skia/GaneshVkRenderEngine.h @@ -28,6 +28,7 @@ protected: std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override; void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override; + void appendBackendSpecificInfoToDump(std::string& result) override; private: GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp index 390ad6efd1..a9332fa4e1 100644 --- a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp +++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp @@ -25,6 +25,7 @@ #include <include/gpu/graphite/Recording.h> #include <include/gpu/graphite/vk/VulkanGraphiteTypes.h> +#include <android-base/stringprintf.h> #include <log/log_main.h> #include <sync/sync.h> @@ -33,6 +34,8 @@ namespace android::renderengine::skia { +using base::StringAppendF; + std::unique_ptr<GraphiteVkRenderEngine> GraphiteVkRenderEngine::create( const RenderEngineCreationArgs& args) { std::unique_ptr<GraphiteVkRenderEngine> engine(new GraphiteVkRenderEngine(args)); @@ -139,4 +142,9 @@ base::unique_fd GraphiteVkRenderEngine::flushAndSubmit(SkiaGpuContext* context, return drawFenceFd; } +void GraphiteVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { + StringAppendF(&result, "\n ------------RE Vulkan (Graphite)----------\n"); + SkiaVkRenderEngine::appendBackendSpecificInfoToDump(result); +} + } // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.h b/libs/renderengine/skia/GraphiteVkRenderEngine.h index cf24a3b756..33a47f1a7f 100644 --- a/libs/renderengine/skia/GraphiteVkRenderEngine.h +++ b/libs/renderengine/skia/GraphiteVkRenderEngine.h @@ -30,6 +30,7 @@ protected: std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override; void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override; + void appendBackendSpecificInfoToDump(std::string& result) override; private: GraphiteVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 4ef7d5bccb..ddae9fc78f 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -541,7 +541,7 @@ int SkiaGLRenderEngine::getContextPriority() { void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { const GLExtensions& extensions = GLExtensions::getInstance(); - StringAppendF(&result, "\n ------------RE GLES------------\n"); + StringAppendF(&result, "\n ------------RE GLES (Ganesh)------------\n"); StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion()); StringAppendF(&result, "%s\n", extensions.getEGLExtensions()); StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(), diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index e62639f3c3..b9869672de 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -543,6 +543,12 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader( } } + if (graphicBuffer && parameters.layer.luts) { + shader = mLutShader.lutShader(shader, parameters.layer.luts, + parameters.layer.sourceDataspace, + toSkColorSpace(parameters.outputDataSpace)); + } + if (parameters.requiresLinearEffect) { const auto format = targetBuffer != nullptr ? std::optional<ui::PixelFormat>( @@ -567,8 +573,10 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader( } // disable tonemapping if we already locally tonemapped - auto inputDataspace = - usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace; + // skip tonemapping if the luts is in use + auto inputDataspace = usingLocalTonemap || (graphicBuffer && parameters.layer.luts) + ? parameters.outputDataSpace + : parameters.layer.sourceDataspace; auto effect = shaders::LinearEffect{.inputDataspace = inputDataspace, .outputDataspace = parameters.outputDataSpace, diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index b5f8898263..7be4c253e7 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -39,6 +39,7 @@ #include "filters/BlurFilter.h" #include "filters/EdgeExtensionShaderFactory.h" #include "filters/LinearEffect.h" +#include "filters/LutShader.h" #include "filters/StretchShaderFactory.h" class SkData; @@ -184,6 +185,7 @@ private: StretchShaderFactory mStretchShaderFactory; EdgeExtensionShaderFactory mEdgeExtensionShaderFactory; + LutShader mLutShader; sp<Fence> mLastDrawFence; BlurFilter* mBlurFilter = nullptr; diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index 677a2b63b2..177abe6c9f 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -169,24 +169,26 @@ int SkiaVkRenderEngine::getContextPriority() { } void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { - StringAppendF(&result, "\n ------------RE Vulkan----------\n"); - StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized()); - StringAppendF(&result, "\n Vulkan protected device initialized: %d\n", + // Subclasses will prepend a backend-specific name / section header + StringAppendF(&result, "Vulkan device initialized: %d\n", sVulkanInterface.isInitialized()); + StringAppendF(&result, "Vulkan protected device initialized: %d\n", sProtectedContentVulkanInterface.isInitialized()); if (!sVulkanInterface.isInitialized()) { return; } - StringAppendF(&result, "\n Instance extensions:\n"); + StringAppendF(&result, "Instance extensions: [\n"); for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) { - StringAppendF(&result, "\n %s\n", name.c_str()); + StringAppendF(&result, " %s\n", name.c_str()); } + StringAppendF(&result, "]\n"); - StringAppendF(&result, "\n Device extensions:\n"); + StringAppendF(&result, "Device extensions: [\n"); for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) { - StringAppendF(&result, "\n %s\n", name.c_str()); + StringAppendF(&result, " %s\n", name.c_str()); } + StringAppendF(&result, "]\n"); } } // namespace skia diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index d2bb3d53cf..88b04df58d 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -81,7 +81,7 @@ protected: SkiaRenderEngine::Contexts createContexts() override; bool supportsProtectedContentImpl() const override; bool useProtectedContextImpl(GrProtected isProtected) override; - void appendBackendSpecificInfoToDump(std::string& result) override; + virtual void appendBackendSpecificInfoToDump(std::string& result) override; // TODO: b/300533018 - refactor this to be non-static static VulkanInterface& getVulkanInterface(bool protectedContext); diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp index db0b133a26..da47aae15b 100644 --- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp +++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp @@ -96,13 +96,17 @@ void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input, const float inverseScale, const float radius, const float alpha) const { - SkRuntimeShaderBuilder blurBuilder(mBlurEffect); - blurBuilder.child("child") = std::move(input); - blurBuilder.uniform("in_inverseScale") = inverseScale; - blurBuilder.uniform("in_blurOffset") = radius; - blurBuilder.uniform("in_crossFade") = alpha; SkPaint paint; - paint.setShader(blurBuilder.makeShader(nullptr)); + if (radius == 0) { + paint.setShader(std::move(input)); + paint.setAlphaf(alpha); + } else { + SkRuntimeShaderBuilder blurBuilder(mBlurEffect); + blurBuilder.child("child") = std::move(input); + blurBuilder.uniform("in_blurOffset") = radius; + blurBuilder.uniform("in_crossFade") = alpha; + paint.setShader(blurBuilder.makeShader(nullptr)); + } paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver); drawSurface->getCanvas()->drawPaint(paint); } @@ -116,32 +120,35 @@ sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uin // Use a variable number of blur passes depending on the radius. The non-integer part of this // calculation is used to mix the final pass into the second-last with an alpha blend. - constexpr int kMaxSurfaces = 4; - const float filterDepth = - std::min(kMaxSurfaces - 1.0f, 1.0f + std::max(0.0f, log2f(radius * kInputScale))); + constexpr int kMaxSurfaces = 3; + const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f); const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth))); - // Render into surfaces downscaled by 1x, 1x, 2x, and 4x from the initial downscale. + // Render into surfaces downscaled by 1x, 2x, and 4x from the initial downscale. sk_sp<SkSurface> surfaces[kMaxSurfaces] = {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr, - filterPasses >= 1 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr, - filterPasses >= 2 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr, - filterPasses >= 3 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr}; - - // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 600. - static const float kWeights[7] = {1.0f, 2.0f, 3.5f, 1.0f, 2.0f, 2.0f, 2.0f}; + filterPasses >= 1 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr, + filterPasses >= 2 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr}; + + // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250. + static const float kWeights[5] = { + 1.0f, // 1st downsampling pass + 1.0f, // 2nd downsampling pass + 1.0f, // 3rd downsampling pass + 0.0f, // 1st upscaling pass. Set to zero to upscale without blurring for performance. + 1.0f, // 2nd upscaling pass + }; // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many // simpler blurs. A transformation is required to approximate the same effect as Gaussian. - float sumSquaredR = powf(kWeights[0] * powf(2.0f, 1), 2.0f); + float sumSquaredR = powf(kWeights[0], 2.0f); for (int i = 0; i < filterPasses; i++) { const float alpha = std::min(1.0f, filterDepth - i); - sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[1 + i], 2.0f); - sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[6 - i], 2.0f); + sumSquaredR += powf(powf(2.0f, i) * alpha * kWeights[1 + i] / kInputScale, 2.0f); + sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[4 - i] / kInputScale, 2.0f); } - // Solve for R = sqrt(sum(r_i^2)). Divide R by hypot(1,1) to find some (x,y) offsets. - const float step = M_SQRT1_2 * - sqrtf(max(0.0f, (powf(radius, 2.0f) - powf(kInverseInputScale, 2.0f)) / sumSquaredR)); + // Solve for R = sqrt(sum(r_i^2)). + const float step = radius * sqrt(1.0f / sumSquaredR); // Start by downscaling and doing the first blur pass. { @@ -162,7 +169,7 @@ sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uin } // Finally blur+upscale back to our original size. for (int i = filterPasses - 1; i >= 0; i--) { - blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[6 - i] * step, + blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step, std::min(1.0f, filterDepth - i)); } return surfaces[0]->makeImageSnapshot(); diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp new file mode 100644 index 0000000000..5e9dfbba3e --- /dev/null +++ b/libs/renderengine/skia/filters/LutShader.cpp @@ -0,0 +1,280 @@ +/* + * 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 "LutShader.h" + +#include <SkM44.h> +#include <SkTileMode.h> +#include <common/trace.h> +#include <cutils/ashmem.h> +#include <math/half.h> +#include <sys/mman.h> +#include <ui/ColorSpace.h> + +#include "include/core/SkColorSpace.h" +#include "src/core/SkColorFilterPriv.h" + +using aidl::android::hardware::graphics::composer3::LutProperties; + +namespace android { +namespace renderengine { +namespace skia { + +static const SkString kShader = SkString(R"( + uniform shader image; + uniform shader lut; + uniform int size; + uniform int key; + uniform int dimension; + uniform vec3 luminanceCoefficients; // for CIE_Y + + vec4 main(vec2 xy) { + float4 rgba = image.eval(xy); + float3 linear = toLinearSrgb(rgba.rgb); + if (dimension == 1) { + // RGB + if (key == 0) { + float indexR = linear.r * float(size - 1); + float indexG = linear.g * float(size - 1); + float indexB = linear.b * float(size - 1); + float gainR = lut.eval(vec2(indexR, 0.0) + 0.5).r; + float gainG = lut.eval(vec2(indexG, 0.0) + 0.5).r; + float gainB = lut.eval(vec2(indexB, 0.0) + 0.5).r; + return float4(linear.r * gainR, linear.g * gainG, linear.b * gainB, rgba.a); + // MAX_RGB + } else if (key == 1) { + float maxRGB = max(linear.r, max(linear.g, linear.b)); + float index = maxRGB * float(size - 1); + float gain = lut.eval(vec2(index, 0.0) + 0.5).r; + return float4(linear * gain, rgba.a); + // CIE_Y + } else if (key == 2) { + float y = dot(linear, luminanceCoefficients) / 3.0; + float index = y * float(size - 1); + float gain = lut.eval(vec2(index, 0.0) + 0.5).r; + return float4(linear * gain, rgba.a); + } + } else if (dimension == 3) { + if (key == 0) { + float tx = linear.r * float(size - 1); + float ty = linear.g * float(size - 1); + float tz = linear.b * float(size - 1); + + // calculate lower and upper bounds for each dimension + int x = int(tx); + int y = int(ty); + int z = int(tz); + + int i000 = x + y * size + z * size * size; + int i100 = i000 + 1; + int i010 = i000 + size; + int i110 = i000 + size + 1; + int i001 = i000 + size * size; + int i101 = i000 + size * size + 1; + int i011 = i000 + size * size + size; + int i111 = i000 + size * size + size + 1; + + // get 1d normalized indices + float c000 = float(i000) / float(size * size * size); + float c100 = float(i100) / float(size * size * size); + float c010 = float(i010) / float(size * size * size); + float c110 = float(i110) / float(size * size * size); + float c001 = float(i001) / float(size * size * size); + float c101 = float(i101) / float(size * size * size); + float c011 = float(i011) / float(size * size * size); + float c111 = float(i111) / float(size * size * size); + + //TODO(b/377984618): support Tetrahedral interpolation + // perform trilinear interpolation + float3 c00 = mix(lut.eval(vec2(c000, 0.0) + 0.5).rgb, + lut.eval(vec2(c100, 0.0) + 0.5).rgb, linear.r); + float3 c01 = mix(lut.eval(vec2(c001, 0.0) + 0.5).rgb, + lut.eval(vec2(c101, 0.0) + 0.5).rgb, linear.r); + float3 c10 = mix(lut.eval(vec2(c010, 0.0) + 0.5).rgb, + lut.eval(vec2(c110, 0.0) + 0.5).rgb, linear.r); + float3 c11 = mix(lut.eval(vec2(c011, 0.0) + 0.5).rgb, + lut.eval(vec2(c111, 0.0) + 0.5).rgb, linear.r); + + float3 c0 = mix(c00, c10, linear.g); + float3 c1 = mix(c01, c11, linear.g); + + float3 val = mix(c0, c1, linear.b); + + return float4(val, rgba.a); + } + } + return rgba; + })"); + +// same as shader::toColorSpace function +// TODO: put this function in a general place +static ColorSpace toColorSpace(ui::Dataspace dataspace) { + switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { + case HAL_DATASPACE_STANDARD_BT709: + return ColorSpace::sRGB(); + case HAL_DATASPACE_STANDARD_DCI_P3: + return ColorSpace::DisplayP3(); + case HAL_DATASPACE_STANDARD_BT2020: + case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE: + return ColorSpace::BT2020(); + case HAL_DATASPACE_STANDARD_ADOBE_RGB: + return ColorSpace::AdobeRGB(); + case HAL_DATASPACE_STANDARD_BT601_625: + case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED: + case HAL_DATASPACE_STANDARD_BT601_525: + case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED: + case HAL_DATASPACE_STANDARD_BT470M: + case HAL_DATASPACE_STANDARD_FILM: + case HAL_DATASPACE_STANDARD_UNSPECIFIED: + default: + return ColorSpace::sRGB(); + } +} + +sk_sp<SkShader> LutShader::generateLutShader(sk_sp<SkShader> input, + const std::vector<float>& buffers, + const int32_t offset, const int32_t length, + const int32_t dimension, const int32_t size, + const int32_t samplingKey, + ui::Dataspace srcDataspace) { + SFTRACE_NAME("lut shader"); + std::vector<half> buffer(length * 4); // 4 is for RGBA + auto d = static_cast<LutProperties::Dimension>(dimension); + if (d == LutProperties::Dimension::ONE_D) { + auto it = buffers.begin() + offset; + std::generate(buffer.begin(), buffer.end(), [it, i = 0]() mutable { + float val = (i++ % 4 == 0) ? *it++ : 0.0f; + return half(val); + }); + } else { + for (int i = 0; i < length; i++) { + buffer[i * 4] = half(buffers[offset + i]); + buffer[i * 4 + 1] = half(buffers[offset + length + i]); + buffer[i * 4 + 2] = half(buffers[offset + length * 2 + i]); + buffer[i * 4 + 3] = half(0); + } + } + /** + * 1D Lut RGB/MAX_RGB + * (R0, 0, 0, 0) + * (R1, 0, 0, 0) + * + * 1D Lut CIE_Y + * (Y0, 0, 0, 0) + * (Y1, 0, 0, 0) + * ... + * + * 3D Lut MAX_RGB + * (R0, G0, B0, 0) + * (R1, G1, B1, 0) + * ... + */ + SkImageInfo info = SkImageInfo::Make(length /* the number of rgba */ * 4, 1, + kRGBA_F16_SkColorType, kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info); + if (!bitmap.installPixels(info, buffer.data(), info.minRowBytes())) { + LOG_ALWAYS_FATAL("unable to install pixels"); + } + + sk_sp<SkImage> lutImage = SkImages::RasterFromBitmap(bitmap); + mBuilder->child("image") = input; + mBuilder->child("lut") = + lutImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, + d == LutProperties::Dimension::ONE_D + ? SkSamplingOptions(SkFilterMode::kLinear) + : SkSamplingOptions()); + + const int uSize = static_cast<int>(size); + const int uKey = static_cast<int>(samplingKey); + const int uDimension = static_cast<int>(dimension); + if (static_cast<LutProperties::SamplingKey>(samplingKey) == LutProperties::SamplingKey::CIE_Y) { + // Use predefined colorspaces of input dataspace so that we can get D65 illuminant + mat3 toXYZMatrix(toColorSpace(srcDataspace).getRGBtoXYZ()); + mBuilder->uniform("luminanceCoefficients") = + SkV3{toXYZMatrix[0][1], toXYZMatrix[1][1], toXYZMatrix[2][1]}; + } else { + mBuilder->uniform("luminanceCoefficients") = SkV3{1.f, 1.f, 1.f}; + } + mBuilder->uniform("size") = uSize; + mBuilder->uniform("key") = uKey; + mBuilder->uniform("dimension") = uDimension; + return mBuilder->makeShader(); +} + +sk_sp<SkShader> LutShader::lutShader(sk_sp<SkShader>& input, + std::shared_ptr<gui::DisplayLuts> displayLuts, + ui::Dataspace srcDataspace, + sk_sp<SkColorSpace> outColorSpace) { + if (mBuilder == nullptr) { + const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(kShader); + mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect); + } + + auto& fd = displayLuts->getLutFileDescriptor(); + if (fd.ok()) { + // de-gamma the image without changing the primaries + SkImage* baseImage = input->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr); + sk_sp<SkColorSpace> baseColorSpace = baseImage && baseImage->colorSpace() + ? baseImage->refColorSpace() + : SkColorSpace::MakeSRGB(); + sk_sp<SkColorSpace> lutMathColorSpace = baseColorSpace->makeLinearGamma(); + input = input->makeWithWorkingColorSpace(lutMathColorSpace); + + auto& offsets = displayLuts->offsets; + auto& lutProperties = displayLuts->lutProperties; + std::vector<float> buffers; + int fullLength = offsets[lutProperties.size() - 1]; + if (lutProperties[lutProperties.size() - 1].dimension == 1) { + fullLength += lutProperties[lutProperties.size() - 1].size; + } else { + fullLength += (lutProperties[lutProperties.size() - 1].size * + lutProperties[lutProperties.size() - 1].size * + lutProperties[lutProperties.size() - 1].size * 3); + } + size_t bufferSize = fullLength * sizeof(float); + + // decode the shared memory of luts + float* ptr = + (float*)mmap(NULL, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0); + if (ptr == MAP_FAILED) { + LOG_ALWAYS_FATAL("mmap failed"); + } + buffers = std::vector<float>(ptr, ptr + fullLength); + munmap(ptr, bufferSize); + + for (size_t i = 0; i < offsets.size(); i++) { + int bufferSizePerLut = (i == offsets.size() - 1) ? buffers.size() - offsets[i] + : offsets[i + 1] - offsets[i]; + // divide by 3 for 3d Lut because of 3 (RGB) channels + if (static_cast<LutProperties::Dimension>(lutProperties[i].dimension) == + LutProperties::Dimension::THREE_D) { + bufferSizePerLut /= 3; + } + input = generateLutShader(input, buffers, offsets[i], bufferSizePerLut, + lutProperties[i].dimension, lutProperties[i].size, + lutProperties[i].samplingKey, srcDataspace); + } + + auto colorXformLutToDst = + SkColorFilterPriv::MakeColorSpaceXform(lutMathColorSpace, outColorSpace); + input = input->makeWithColorFilter(colorXformLutToDst); + } + return input; +} + +} // namespace skia +} // namespace renderengine +} // namespace android
\ No newline at end of file diff --git a/libs/renderengine/skia/filters/LutShader.h b/libs/renderengine/skia/filters/LutShader.h new file mode 100644 index 0000000000..7c62fcae08 --- /dev/null +++ b/libs/renderengine/skia/filters/LutShader.h @@ -0,0 +1,45 @@ +/* + * 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 <SkBitmap.h> +#include <SkImage.h> +#include <SkRuntimeEffect.h> + +#include <aidl/android/hardware/graphics/composer3/LutProperties.h> +#include <gui/DisplayLuts.h> +#include <ui/GraphicTypes.h> + +namespace android { +namespace renderengine { +namespace skia { + +class LutShader { +public: + sk_sp<SkShader> lutShader(sk_sp<SkShader>& input, std::shared_ptr<gui::DisplayLuts> displayLuts, + ui::Dataspace srcDataspace, sk_sp<SkColorSpace> outColorSpace); + +private: + sk_sp<SkShader> generateLutShader(sk_sp<SkShader> input, const std::vector<float>& buffers, + const int32_t offset, const int32_t length, + const int32_t dimension, const int32_t size, + const int32_t samplingKey, ui::Dataspace srcDataspace); + std::unique_ptr<SkRuntimeShaderBuilder> mBuilder; +}; + +} // namespace skia +} // namespace renderengine +} // namespace android diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp index 659666d6b6..a687a37499 100644 --- a/libs/sensor/Android.bp +++ b/libs/sensor/Android.bp @@ -69,6 +69,7 @@ cc_library { static_libs: [ "libsensor_flags_c_lib", + "android.permission.flags-aconfig-cc", ], export_include_dirs: ["include"], diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index a1549ea385..797efbe5df 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -21,6 +21,7 @@ #include <binder/AppOpsManager.h> #include <binder/IPermissionController.h> #include <binder/IServiceManager.h> +#include <android_permission_flags.h> /* * The permission to use for activity recognition sensors (like step counter). @@ -121,7 +122,9 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi break; case SENSOR_TYPE_HEART_RATE: { mStringType = SENSOR_STRING_TYPE_HEART_RATE; - mRequiredPermission = SENSOR_PERMISSION_BODY_SENSORS; + mRequiredPermission = + android::permission::flags::replace_body_sensor_permission_enabled() ? + SENSOR_PERMISSION_READ_HEART_RATE : SENSOR_PERMISSION_BODY_SENSORS; AppOpsManager appOps; mRequiredAppOp = appOps.permissionToOpCode(String16(mRequiredPermission)); mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; @@ -303,7 +306,18 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi } if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor.requiredPermission) { mRequiredPermission = hwSensor.requiredPermission; - if (!strcmp(mRequiredPermission, SENSOR_PERMISSION_BODY_SENSORS)) { + bool requiresBodySensorPermission = + !strcmp(mRequiredPermission, SENSOR_PERMISSION_BODY_SENSORS); + if (android::permission::flags::replace_body_sensor_permission_enabled()) { + if (requiresBodySensorPermission) { + ALOGE("Sensor %s using deprecated Body Sensor permission", mName.c_str()); + } + + AppOpsManager appOps; + // Lookup to see if an AppOp exists for the permission. If none + // does, the default value of -1 is used. + mRequiredAppOp = appOps.permissionToOpCode(String16(mRequiredPermission)); + } else if (requiresBodySensorPermission) { AppOpsManager appOps; mRequiredAppOp = appOps.permissionToOpCode(String16(SENSOR_PERMISSION_BODY_SENSORS)); } diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp index b5c56c5c52..9a2d4f7463 100644 --- a/libs/tracing_perfetto/Android.bp +++ b/libs/tracing_perfetto/Android.bp @@ -47,4 +47,6 @@ cc_library_shared { ], host_supported: true, + // for vndbinder + vendor_available: true, } diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp index 9a0042aee5..a58bc77131 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -236,6 +236,7 @@ void registerWithPerfetto(bool test) { std::call_once(registration, [test]() { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM; + args.shmem_size_hint_kb = 1024; PerfettoProducerInit(args); PerfettoTeInit(); PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); @@ -253,15 +254,31 @@ void perfettoTraceEnd(const struct PerfettoTeCategory& category) { void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name, const char* trackName, uint64_t cookie) { PERFETTO_TE( - category, PERFETTO_TE_SLICE_BEGIN(name), - PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid())); + category, PERFETTO_TE_SLICE_BEGIN(name), + PERFETTO_TE_PROTO_TRACK( + PerfettoTeNamedTrackUuid(trackName, cookie, + PerfettoTeProcessTrackUuid()), + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_TrackDescriptor_atrace_name_field_number, + trackName), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_TrackDescriptor_parent_uuid_field_number, + PerfettoTeProcessTrackUuid()))); } void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category, const char* trackName, uint64_t cookie) { - PERFETTO_TE( - category, PERFETTO_TE_SLICE_END(), - PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid())); + PERFETTO_TE( + category, PERFETTO_TE_SLICE_END(), + PERFETTO_TE_PROTO_TRACK( + PerfettoTeNamedTrackUuid(trackName, cookie, + PerfettoTeProcessTrackUuid()), + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_TrackDescriptor_atrace_name_field_number, + trackName), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_TrackDescriptor_parent_uuid_field_number, + PerfettoTeProcessTrackUuid()))); } void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name, @@ -281,14 +298,35 @@ void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category, const char* trackName, const char* name) { PERFETTO_TE( - category, PERFETTO_TE_INSTANT(name), - PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid())); + category, PERFETTO_TE_INSTANT(name), + PERFETTO_TE_PROTO_TRACK( + PerfettoTeNamedTrackUuid(trackName, 1, + PerfettoTeProcessTrackUuid()), + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_TrackDescriptor_atrace_name_field_number, + trackName), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_TrackDescriptor_parent_uuid_field_number, + PerfettoTeProcessTrackUuid()))); } void perfettoTraceCounter(const struct PerfettoTeCategory& category, - [[maybe_unused]] const char* name, int64_t value) { - PERFETTO_TE(category, PERFETTO_TE_COUNTER(), - PERFETTO_TE_INT_COUNTER(value)); + const char* name, int64_t value) { + PERFETTO_TE( + category, PERFETTO_TE_COUNTER(), + PERFETTO_TE_PROTO_TRACK( + PerfettoTeCounterTrackUuid(name, + PerfettoTeProcessTrackUuid()), + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_TrackDescriptor_atrace_name_field_number, + name), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_TrackDescriptor_parent_uuid_field_number, + PerfettoTeProcessTrackUuid()), + PERFETTO_TE_PROTO_FIELD_BYTES( + perfetto_protos_TrackDescriptor_counter_field_number, + PERFETTO_NULL, 0)), + PERFETTO_TE_INT_COUNTER(value)); } } // namespace internal diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index 12230f99d2..87e213e394 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -136,6 +136,7 @@ cc_library_shared { "GraphicBuffer.cpp", "GraphicBufferAllocator.cpp", "GraphicBufferMapper.cpp", + "PictureProfileHandle.cpp", "PixelFormat.cpp", "PublicFormat.cpp", "StaticAsserts.cpp", diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp index 8b13d78840..8d6f74b605 100644 --- a/libs/ui/DisplayIdentification.cpp +++ b/libs/ui/DisplayIdentification.cpp @@ -19,9 +19,12 @@ #include <algorithm> #include <cctype> +#include <cstdint> #include <numeric> #include <optional> #include <span> +#include <string> +#include <string_view> #include <ftl/hash.h> #include <log/log.h> @@ -194,6 +197,21 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { const uint16_t productId = static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8)); + // Bytes 12-15: display serial number, in little-endian (LSB). This field is + // optional and its absence is marked by having all bytes set to 0x00. + // Values do not represent ASCII characters. + constexpr size_t kSerialNumberOffset = 12; + if (edid.size() < kSerialNumberOffset + sizeof(uint32_t)) { + ALOGE("Invalid EDID: block zero S/N is truncated."); + return {}; + } + const uint32_t blockZeroSerialNumber = edid[kSerialNumberOffset] + + (edid[kSerialNumberOffset + 1] << 8) + (edid[kSerialNumberOffset + 2] << 16) + + (edid[kSerialNumberOffset + 3] << 24); + const auto hashedBlockZeroSNOpt = blockZeroSerialNumber == 0 + ? std::nullopt + : ftl::stable_hash(std::string_view(std::to_string(blockZeroSerialNumber))); + constexpr size_t kManufactureWeekOffset = 16; if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) { ALOGE("Invalid EDID: manufacture week is truncated."); @@ -212,6 +230,15 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { ALOGW_IF(manufactureOrModelYear <= 0xf, "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf]."); + constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21; + constexpr size_t kMaxVerticalPhysicalSizeOffset = 22; + if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) { + ALOGE("Invalid EDID: display's physical size is truncated."); + return {}; + } + ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset], + edid[kMaxVerticalPhysicalSizeOffset]); + constexpr size_t kDescriptorOffset = 54; if (edid.size() < kDescriptorOffset) { ALOGE("Invalid EDID: descriptors are missing."); @@ -222,7 +249,8 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { view = view.subspan(kDescriptorOffset); std::string_view displayName; - std::string_view serialNumber; + std::string_view descriptorBlockSerialNumber; + std::optional<uint64_t> hashedDescriptorBlockSNOpt = std::nullopt; std::string_view asciiText; ui::Size preferredDTDPixelSize; ui::Size preferredDTDPhysicalSize; @@ -247,7 +275,10 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { asciiText = parseEdidText(descriptor); break; case 0xff: - serialNumber = parseEdidText(descriptor); + descriptorBlockSerialNumber = parseEdidText(descriptor); + hashedDescriptorBlockSNOpt = descriptorBlockSerialNumber.empty() + ? std::nullopt + : ftl::stable_hash(descriptorBlockSerialNumber); break; } } else if (isDetailedTimingDescriptor(view)) { @@ -288,7 +319,7 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { if (modelString.empty()) { ALOGW("Invalid EDID: falling back to serial number due to missing display name."); - modelString = serialNumber; + modelString = descriptorBlockSerialNumber; } if (modelString.empty()) { ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number."); @@ -341,11 +372,14 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) { return Edid{ .manufacturerId = manufacturerId, .productId = productId, + .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt, + .hashedDescriptorBlockSerialNumberOpt = hashedDescriptorBlockSNOpt, .pnpId = *pnpId, .modelHash = modelHash, .displayName = displayName, .manufactureOrModelYear = manufactureOrModelYear, .manufactureWeek = manufactureWeek, + .physicalSizeInCm = maxPhysicalSizeInCm, .cea861Block = cea861Block, .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor, }; diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp index 1ebe5973fa..a9f7cedab6 100644 --- a/libs/ui/GraphicBufferAllocator.cpp +++ b/libs/ui/GraphicBufferAllocator.cpp @@ -89,14 +89,14 @@ void GraphicBufferAllocator::dump(std::string& result, bool less) const { uint64_t total = 0; result.append("GraphicBufferAllocator buffers:\n"); const size_t count = list.size(); - StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size", + StringAppendF(&result, "%18s | %12s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size", "W (Stride) x H", "Layers", "Format", "Usage", "Requestor"); for (size_t i = 0; i < count; i++) { const alloc_rec_t& rec(list.valueAt(i)); std::string sizeStr = (rec.size) ? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0) : "unknown"; - StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n", + StringAppendF(&result, "%18p | %12s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n", list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height, rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str()); total += rec.size; diff --git a/libs/ui/PictureProfileHandle.cpp b/libs/ui/PictureProfileHandle.cpp new file mode 100644 index 0000000000..0701e906f0 --- /dev/null +++ b/libs/ui/PictureProfileHandle.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 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 <ui/PictureProfileHandle.h> + +#include <format> + +namespace android { + +const PictureProfileHandle PictureProfileHandle::NONE(0); + +::std::string toString(const PictureProfileHandle& handle) { + return std::format("{:#010x}", handle.getId()); +} + +} // namespace android diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp index c9663edd7a..dbc0884bf4 100644 --- a/libs/ui/PublicFormat.cpp +++ b/libs/ui/PublicFormat.cpp @@ -30,6 +30,7 @@ int mapPublicFormatToHalFormat(PublicFormat f) { case PublicFormat::DEPTH_POINT_CLOUD: case PublicFormat::DEPTH_JPEG: case PublicFormat::HEIC: + case PublicFormat::HEIC_ULTRAHDR: case PublicFormat::JPEG_R: return HAL_PIXEL_FORMAT_BLOB; case PublicFormat::DEPTH16: @@ -74,6 +75,9 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { case PublicFormat::HEIC: dataspace = Dataspace::HEIF; break; + case PublicFormat::HEIC_ULTRAHDR: + dataspace = Dataspace::HEIF_ULTRAHDR; + break; case PublicFormat::JPEG_R: dataspace = Dataspace::JPEG_R; break; @@ -153,6 +157,9 @@ PublicFormat mapHalFormatDataspaceToPublicFormat(int format, android_dataspace d return PublicFormat::DEPTH_JPEG; } else if (dataSpace == static_cast<android_dataspace>(Dataspace::JPEG_R)) { return PublicFormat::JPEG_R; + } else if (dataSpace == static_cast<android_dataspace>( + Dataspace::HEIF_ULTRAHDR)) { + return PublicFormat::HEIC_ULTRAHDR; }else { // Assume otherwise-marked blobs are also JPEG return PublicFormat::JPEG; diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h index 648e024d86..cf67d7bf93 100644 --- a/libs/ui/include/ui/DisplayIdentification.h +++ b/libs/ui/include/ui/DisplayIdentification.h @@ -69,11 +69,14 @@ struct Cea861ExtensionBlock : ExtensionBlock { struct Edid { uint16_t manufacturerId; uint16_t productId; + std::optional<uint64_t> hashedBlockZeroSerialNumberOpt; + std::optional<uint64_t> hashedDescriptorBlockSerialNumberOpt; PnpId pnpId; uint32_t modelHash; std::string_view displayName; uint8_t manufactureOrModelYear; uint8_t manufactureWeek; + ui::Size physicalSizeInCm; std::optional<Cea861ExtensionBlock> cea861Block; std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor; }; diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h index 0b77754455..9d97151155 100644 --- a/libs/ui/include/ui/DynamicDisplayInfo.h +++ b/libs/ui/include/ui/DynamicDisplayInfo.h @@ -22,6 +22,7 @@ #include <optional> #include <vector> +#include <ui/FrameRateCategoryRate.h> #include <ui/GraphicTypes.h> #include <ui/HdrCapabilities.h> @@ -53,6 +54,14 @@ struct DynamicDisplayInfo { ui::DisplayModeId preferredBootDisplayMode; std::optional<ui::DisplayMode> getActiveDisplayMode() const; + + bool hasArrSupport; + + // Represents frame rate for FrameRateCategory Normal and High. + ui::FrameRateCategoryRate frameRateCategoryRate; + + // All the refresh rates supported for the default display mode. + std::vector<float> supportedRefreshRates; }; } // namespace android::ui diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h index 4c9c7b7ef1..4366db539f 100644 --- a/libs/ui/include/ui/FloatRect.h +++ b/libs/ui/include/ui/FloatRect.h @@ -51,6 +51,9 @@ public: float bottom = 0.0f; constexpr bool isEmpty() const { return !(left < right && top < bottom); } + + // a valid rectangle has a non negative width and height + inline bool isValid() const { return (getWidth() >= 0) && (getHeight() >= 0); } }; inline bool operator==(const FloatRect& a, const FloatRect& b) { diff --git a/libs/ui/include/ui/FrameRateCategoryRate.h b/libs/ui/include/ui/FrameRateCategoryRate.h new file mode 100644 index 0000000000..9c392d9bc8 --- /dev/null +++ b/libs/ui/include/ui/FrameRateCategoryRate.h @@ -0,0 +1,35 @@ +/* + * 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 + +namespace android::ui { + +// Represents frame rate for FrameRateCategory Normal and High. +class FrameRateCategoryRate { +public: + FrameRateCategoryRate(float normal = 0, float high = 0) : mNormal(normal), mHigh(high) {} + + float getNormal() const { return mNormal; } + + float getHigh() const { return mHigh; } + +private: + float mNormal; + float mHigh; +}; + +} // namespace android::ui
\ No newline at end of file diff --git a/libs/ui/include/ui/PictureProfileHandle.h b/libs/ui/include/ui/PictureProfileHandle.h new file mode 100644 index 0000000000..f8406501b4 --- /dev/null +++ b/libs/ui/include/ui/PictureProfileHandle.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 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 <stdint.h> +#include <array> +#include <string> + +namespace android { + +/** + * An opaque value that uniquely identifies a picture profile, or a set of parameters, which + * describes the configuration of a picture processing pipeline that is applied to a graphic buffer + * to enhance its quality prior to rendering on the display. + */ +typedef int64_t PictureProfileId; + +/** + * A picture profile handle wraps the picture profile ID for type-safety, and represents an opaque + * handle that doesn't have the performance drawbacks of Binders. + */ +class PictureProfileHandle { +public: + // A profile that represents no picture processing. + static const PictureProfileHandle NONE; + + PictureProfileHandle() { *this = NONE; } + explicit PictureProfileHandle(PictureProfileId id) : mId(id) {} + + PictureProfileId const& getId() const { return mId; } + + inline bool operator==(const PictureProfileHandle& rhs) { return mId == rhs.mId; } + inline bool operator!=(const PictureProfileHandle& rhs) { return !(*this == rhs); } + + // Is the picture profile effectively null, or not-specified? + inline bool operator!() const { return mId == NONE.mId; } + + operator bool() const { return !!*this; } + + friend ::std::string toString(const PictureProfileHandle& handle); + +private: + PictureProfileId mId; +}; + +} // namespace android diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h index 7b7f5022d9..7c17763860 100644 --- a/libs/ui/include/ui/PublicFormat.h +++ b/libs/ui/include/ui/PublicFormat.h @@ -59,6 +59,7 @@ enum class PublicFormat { DEPTH_JPEG = 0x69656963, JPEG_R = 0x1005, HEIC = 0x48454946, + HEIC_ULTRAHDR = 0x1006, YCBCR_P210 = 0x3c, }; diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h index 2eb9330cc9..2307b44eb2 100644 --- a/libs/ui/include/ui/Rect.h +++ b/libs/ui/include/ui/Rect.h @@ -74,12 +74,10 @@ public: } inline explicit Rect(const FloatRect& floatRect) { - // Ideally we would use std::round, but we don't want to add an STL - // dependency here, so we use an approximation - left = static_cast<int32_t>(floatRect.left + 0.5f); - top = static_cast<int32_t>(floatRect.top + 0.5f); - right = static_cast<int32_t>(floatRect.right + 0.5f); - bottom = static_cast<int32_t>(floatRect.bottom + 0.5f); + left = static_cast<int32_t>(std::round(floatRect.left)); + top = static_cast<int32_t>(std::round(floatRect.top)); + right = static_cast<int32_t>(std::round(floatRect.right)); + bottom = static_cast<int32_t>(std::round(floatRect.bottom)); } inline explicit Rect(const ui::Size& size) { diff --git a/libs/ui/include_types/ui/HdrRenderTypeUtils.h b/libs/ui/include_types/ui/HdrRenderTypeUtils.h index b0af878cdb..70c50f07e5 100644 --- a/libs/ui/include_types/ui/HdrRenderTypeUtils.h +++ b/libs/ui/include_types/ui/HdrRenderTypeUtils.h @@ -61,4 +61,24 @@ inline HdrRenderType getHdrRenderType(ui::Dataspace dataspace, return HdrRenderType::SDR; } -} // namespace android
\ No newline at end of file +/** + * Returns the maximum headroom allowed for this content under "idealized" + * display conditions (low surround luminance, high-enough display brightness). + * + * TODO: take into account hdr metadata, but square it with the fact that some + * HLG content has CTA.861-3 metadata + */ +inline float getIdealizedMaxHeadroom(ui::Dataspace dataspace) { + const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK; + + switch (transfer) { + case HAL_DATASPACE_TRANSFER_ST2084: + return 10000.0f / 203.0f; + case HAL_DATASPACE_TRANSFER_HLG: + return 1000.0f / 203.0f; + default: + return 1.0f; + } +} + +} // namespace android diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp index 76e3f66e1b..d1699e79b6 100644 --- a/libs/ui/tests/DisplayIdentification_test.cpp +++ b/libs/ui/tests/DisplayIdentification_test.cpp @@ -33,7 +33,7 @@ namespace android { namespace { const unsigned char kInternalEdid[] = - "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00" + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x4e\x61\xbc\x00" "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27" "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30" @@ -54,7 +54,7 @@ const unsigned char kExternalEdid[] = // Extended EDID with timing extension. const unsigned char kExternalEedid[] = - "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00" + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\xb1\x7f\x39\x05" "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26" "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00" "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c" @@ -112,7 +112,7 @@ const unsigned char kHisenseTvEdid[] = "\x07"; const unsigned char kCtlDisplayEdid[] = - "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x00\x00\x00\x00" + "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x30\x41\xab\x00" "\xff\x17\x01\x04\xa5\x34\x1d\x78\x3a\xa7\x25\xa4\x57\x51\xa0\x26" "\x10\x50\x54\xbf\xef\x80\xb3\x00\xa9\x40\x95\x00\x81\x40\x81\x80" "\x95\x0f\x71\x4f\x90\x40\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c" @@ -191,8 +191,13 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("121AT11-801"), 626564263); EXPECT_TRUE(edid->displayName.empty()); EXPECT_EQ(12610, edid->productId); + EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("12345678"), edid->hashedBlockZeroSerialNumberOpt.value()); + EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); EXPECT_EQ(21, edid->manufactureOrModelYear); EXPECT_EQ(0, edid->manufactureWeek); + EXPECT_EQ(26, edid->physicalSizeInCm.width); + EXPECT_EQ(16, edid->physicalSizeInCm.height); EXPECT_FALSE(edid->cea861Block); EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width); EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height); @@ -207,8 +212,14 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("HP ZR30w"), 918492362); EXPECT_EQ("HP ZR30w", edid->displayName); EXPECT_EQ(10348, edid->productId); + EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value()); + EXPECT_TRUE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("CN4202137Q"), edid->hashedDescriptorBlockSerialNumberOpt.value()); EXPECT_EQ(22, edid->manufactureOrModelYear); EXPECT_EQ(2, edid->manufactureWeek); + EXPECT_EQ(64, edid->physicalSizeInCm.width); + EXPECT_EQ(40, edid->physicalSizeInCm.height); EXPECT_FALSE(edid->cea861Block); EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width); EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height); @@ -223,8 +234,13 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("SAMSUNG"), 1201368132); EXPECT_EQ("SAMSUNG", edid->displayName); EXPECT_EQ(2302, edid->productId); + EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("87654321"), edid->hashedBlockZeroSerialNumberOpt.value()); + EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); EXPECT_EQ(21, edid->manufactureOrModelYear); EXPECT_EQ(41, edid->manufactureWeek); + EXPECT_EQ(16, edid->physicalSizeInCm.width); + EXPECT_EQ(9, edid->physicalSizeInCm.height); ASSERT_TRUE(edid->cea861Block); ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); auto physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; @@ -245,8 +261,13 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("Panasonic-TV"), 3876373262); EXPECT_EQ("Panasonic-TV", edid->displayName); EXPECT_EQ(41622, edid->productId); + EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value()); + EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); EXPECT_EQ(29, edid->manufactureOrModelYear); EXPECT_EQ(0, edid->manufactureWeek); + EXPECT_EQ(128, edid->physicalSizeInCm.width); + EXPECT_EQ(72, edid->physicalSizeInCm.height); ASSERT_TRUE(edid->cea861Block); ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; @@ -267,8 +288,12 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("Hisense"), 2859844809); EXPECT_EQ("Hisense", edid->displayName); EXPECT_EQ(0, edid->productId); + EXPECT_FALSE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); EXPECT_EQ(29, edid->manufactureOrModelYear); EXPECT_EQ(18, edid->manufactureWeek); + EXPECT_EQ(0, edid->physicalSizeInCm.width); + EXPECT_EQ(0, edid->physicalSizeInCm.height); ASSERT_TRUE(edid->cea861Block); ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock); physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress; @@ -289,8 +314,13 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(hash("LP2361"), 1523181158); EXPECT_EQ("LP2361", edid->displayName); EXPECT_EQ(9373, edid->productId); + EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value()); + EXPECT_EQ(ftl::stable_hash("11223344"), edid->hashedBlockZeroSerialNumberOpt.value()); + EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value()); EXPECT_EQ(23, edid->manufactureOrModelYear); EXPECT_EQ(0xff, edid->manufactureWeek); + EXPECT_EQ(52, edid->physicalSizeInCm.width); + EXPECT_EQ(29, edid->physicalSizeInCm.height); ASSERT_TRUE(edid->cea861Block); EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock); EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width); @@ -447,4 +477,4 @@ TEST(DisplayIdentificationTest, getVirtualDisplayId) { } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file +#pragma clang diagnostic pop // ignored "-Wextra" diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp index 9cc36bb15b..c3c8bd9fa4 100644 --- a/libs/ui/tests/Rect_test.cpp +++ b/libs/ui/tests/Rect_test.cpp @@ -99,6 +99,16 @@ TEST(RectTest, constructFromFloatRect) { EXPECT_EQ(30, rect.right); EXPECT_EQ(40, rect.bottom); } + + EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.f, 1.f, -1.f, 0.f))); + EXPECT_EQ(Rect(100000, 100000, -100000, -100000), + Rect(FloatRect(100000.f, 100000.f, -100000.f, -100000.f))); + + // round down if < .5 + EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.4f, 1.1f, -1.499f, 0.1f))); + + // round up if >= .5 + EXPECT_EQ(Rect(20, 20, -20, -20), Rect(FloatRect(19.5f, 19.9f, -19.5f, -19.9f))); } TEST(RectTest, makeInvalid) { diff --git a/libs/vibrator/TEST_MAPPING b/libs/vibrator/TEST_MAPPING index d782b43b57..e206761efa 100644 --- a/libs/vibrator/TEST_MAPPING +++ b/libs/vibrator/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "libvibrator_test" } |