diff options
author | 2025-03-03 17:54:37 -0800 | |
---|---|---|
committer | 2025-03-05 22:37:03 -0800 | |
commit | 15bfd3dd7f517f5d8b0ba1c2cc231f16d03d4405 (patch) | |
tree | 32dff236c941635c67a5f5e3df713c90205823d4 | |
parent | 2f706495c6b09ded68dac3628634bcb76be03495 (diff) |
Introduce TransactionState.
Encapsulate all the data inside transaction that we need
to pass into SurfaceFlinger. This will remove some
duplicate parcelling logic and clean up the interface
into SF.
Flag: EXEMPT refactor
Bug: 385156191
Test: presubmit
Change-Id: Ia6fab8539e48900700524a127cbcbbebd9acaf7a
-rw-r--r-- | libs/gui/Android.bp | 1 | ||||
-rw-r--r-- | libs/gui/TransactionState.cpp | 263 | ||||
-rw-r--r-- | libs/gui/include/gui/TransactionState.h | 94 | ||||
-rw-r--r-- | libs/gui/tests/Android.bp | 1 | ||||
-rw-r--r-- | libs/gui/tests/TransactionState_test.cpp | 284 |
5 files changed, 643 insertions, 0 deletions
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 5ab31dbaba..b5fa321fc2 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -281,6 +281,7 @@ filegroup { "SurfaceControl.cpp", "SurfaceComposerClient.cpp", "SyncFeatures.cpp", + "TransactionState.cpp", "VsyncEventData.cpp", "view/Surface.cpp", "WindowInfosListenerReporter.cpp", diff --git a/libs/gui/TransactionState.cpp b/libs/gui/TransactionState.cpp new file mode 100644 index 0000000000..9e09bc2644 --- /dev/null +++ b/libs/gui/TransactionState.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2025 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 "TransactionState" +#include <gui/LayerState.h> +#include <gui/SurfaceComposerClient.h> +#include <gui/TransactionState.h> +#include <private/gui/ParcelUtils.h> +#include <algorithm> + +namespace android { + +status_t TransactionState::writeToParcel(Parcel* parcel) const { + SAFE_PARCEL(parcel->writeUint64, mId); + SAFE_PARCEL(parcel->writeUint32, mFlags); + SAFE_PARCEL(parcel->writeInt64, mDesiredPresentTime); + SAFE_PARCEL(parcel->writeBool, mIsAutoTimestamp); + SAFE_PARCEL(parcel->writeParcelable, mFrameTimelineInfo); + SAFE_PARCEL(parcel->writeStrongBinder, mApplyToken); + SAFE_PARCEL(parcel->writeBool, mMayContainBuffer); + SAFE_PARCEL(parcel->writeBool, mLogCallPoints); + + SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mDisplayStates.size())); + for (auto const& displayState : mDisplayStates) { + displayState.write(*parcel); + } + SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mComposerStates.size())); + for (auto const& composerState : mComposerStates) { + composerState.write(*parcel); + } + + mInputWindowCommands.write(*parcel); + SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mUncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : mUncacheBuffers) { + SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); + } + + SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mMergedTransactionIds.size())); + for (auto mergedTransactionId : mMergedTransactionIds) { + SAFE_PARCEL(parcel->writeUint64, mergedTransactionId); + } + + SAFE_PARCEL(parcel->writeBool, mHasListenerCallbacks); + SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mListenerCallbacks.size())); + for (const auto& [listener, callbackIds] : mListenerCallbacks) { + SAFE_PARCEL(parcel->writeStrongBinder, listener); + SAFE_PARCEL(parcel->writeParcelableVector, callbackIds); + } + + return NO_ERROR; +} + +status_t TransactionState::readFromParcel(const Parcel* parcel) { + SAFE_PARCEL(parcel->readUint64, &mId); + SAFE_PARCEL(parcel->readUint32, &mFlags); + SAFE_PARCEL(parcel->readInt64, &mDesiredPresentTime); + SAFE_PARCEL(parcel->readBool, &mIsAutoTimestamp); + SAFE_PARCEL(parcel->readParcelable, &mFrameTimelineInfo); + SAFE_PARCEL(parcel->readNullableStrongBinder, &mApplyToken); + SAFE_PARCEL(parcel->readBool, &mMayContainBuffer); + SAFE_PARCEL(parcel->readBool, &mLogCallPoints); + + uint32_t count; + SAFE_PARCEL_READ_SIZE(parcel->readUint32, &count, parcel->dataSize()) + mDisplayStates.clear(); + mDisplayStates.reserve(count); + for (size_t i = 0; i < count; i++) { + DisplayState displayState; + if (displayState.read(*parcel) == BAD_VALUE) { + return BAD_VALUE; + } + mDisplayStates.emplace_back(std::move(displayState)); + } + + SAFE_PARCEL_READ_SIZE(parcel->readUint32, &count, parcel->dataSize()) + mComposerStates.clear(); + mComposerStates.reserve(count); + for (size_t i = 0; i < count; i++) { + ComposerState composerState; + if (composerState.read(*parcel) == BAD_VALUE) { + return BAD_VALUE; + } + mComposerStates.emplace_back(std::move(composerState)); + } + + if (status_t status = mInputWindowCommands.read(*parcel) != NO_ERROR) { + return status; + } + + SAFE_PARCEL_READ_SIZE(parcel->readUint32, &count, parcel->dataSize()) + mUncacheBuffers.clear(); + mUncacheBuffers.reserve(count); + for (size_t i = 0; i < count; i++) { + client_cache_t client_cache; + sp<IBinder> tmpBinder; + SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); + client_cache.token = tmpBinder; + SAFE_PARCEL(parcel->readUint64, &client_cache.id); + mUncacheBuffers.emplace_back(std::move(client_cache)); + } + + SAFE_PARCEL_READ_SIZE(parcel->readUint32, &count, parcel->dataSize()) + mMergedTransactionIds.clear(); + mMergedTransactionIds.resize(count); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(parcel->readUint64, &mMergedTransactionIds[i]); + } + + SAFE_PARCEL(parcel->readBool, &mHasListenerCallbacks); + SAFE_PARCEL_READ_SIZE(parcel->readUint32, &count, parcel->dataSize()); + mListenerCallbacks.clear(); + mListenerCallbacks.reserve(count); + for (uint32_t i = 0; i < count; i++) { + sp<IBinder> tmpBinder; + SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); + std::vector<CallbackId> callbackIds; + SAFE_PARCEL(parcel->readParcelableVector, &callbackIds); + mListenerCallbacks.emplace_back(tmpBinder, callbackIds); + } + + return NO_ERROR; +} + +void TransactionState::merge(TransactionState&& other, + const std::function<void(layer_state_t&)>& onBufferOverwrite) { + while (mMergedTransactionIds.size() + other.mMergedTransactionIds.size() > + MAX_MERGE_HISTORY_LENGTH - 1 && + mMergedTransactionIds.size() > 0) { + mMergedTransactionIds.pop_back(); + } + if (other.mMergedTransactionIds.size() == MAX_MERGE_HISTORY_LENGTH) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end() - 1); + } else if (other.mMergedTransactionIds.size() > 0u) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end()); + } + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId); + + for (auto const& otherState : other.mComposerStates) { + if (auto it = std::find_if(mComposerStates.begin(), mComposerStates.end(), + [&otherState](const auto& composerState) { + return composerState.state.surface == + otherState.state.surface; + }); + it != mComposerStates.end()) { + if (otherState.state.what & layer_state_t::eBufferChanged) { + onBufferOverwrite(it->state); + } + it->state.merge(otherState.state); + } else { + mComposerStates.push_back(otherState); + } + } + + for (auto const& state : other.mDisplayStates) { + if (auto it = std::find_if(mDisplayStates.begin(), mDisplayStates.end(), + [&state](const auto& displayState) { + return displayState.token == state.token; + }); + it != mDisplayStates.end()) { + it->merge(state); + } else { + mDisplayStates.push_back(state); + } + } + + for (const auto& cacheId : other.mUncacheBuffers) { + mUncacheBuffers.push_back(cacheId); + } + + mInputWindowCommands.merge(other.mInputWindowCommands); + // TODO(b/385156191) Consider merging desired present time. + mFlags |= other.mFlags; + mMayContainBuffer |= other.mMayContainBuffer; + mLogCallPoints |= other.mLogCallPoints; + + // mApplyToken is explicitly not merged. Token should be set before applying the transactions to + // make synchronization decisions a bit simpler. + mergeFrameTimelineInfo(other.mFrameTimelineInfo); + other.clear(); +} + +// copied from FrameTimelineInfo::merge() +void TransactionState::mergeFrameTimelineInfo(const FrameTimelineInfo& other) { + // When merging vsync Ids we take the oldest valid one + if (mFrameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID && + other.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + if (other.vsyncId > mFrameTimelineInfo.vsyncId) { + mFrameTimelineInfo = other; + } + } else if (mFrameTimelineInfo.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) { + mFrameTimelineInfo = other; + } +} + +void TransactionState::clear() { + mComposerStates.clear(); + mDisplayStates.clear(); + mListenerCallbacks.clear(); + mHasListenerCallbacks = false; + mInputWindowCommands.clear(); + mUncacheBuffers.clear(); + mDesiredPresentTime = 0; + mIsAutoTimestamp = true; + mApplyToken = nullptr; + mFrameTimelineInfo = {}; + mMergedTransactionIds.clear(); + mFlags = 0; + mMayContainBuffer = false; + mLogCallPoints = false; +} + +layer_state_t* TransactionState::getLayerState(const sp<SurfaceControl>& sc) { + auto handle = sc->getLayerStateHandle(); + if (auto it = std::find_if(mComposerStates.begin(), mComposerStates.end(), + [&handle](const auto& composerState) { + return composerState.state.surface == handle; + }); + it != mComposerStates.end()) { + return &it->state; + } + + // we don't have it, add an initialized layer_state to our list + ComposerState s; + s.state.surface = handle; + s.state.layerId = sc->getLayerId(); + mComposerStates.push_back(s); + + return &mComposerStates.back().state; +} + +DisplayState& TransactionState::getDisplayState(const sp<IBinder>& token) { + if (auto it = std::find_if(mDisplayStates.begin(), mDisplayStates.end(), + [token](const auto& display) { return display.token == token; }); + it != mDisplayStates.end()) { + return *it; + } + + // If display state doesn't exist, add a new one. + DisplayState s; + s.token = token; + mDisplayStates.push_back(s); + return mDisplayStates.back(); +} + +}; // namespace android diff --git a/libs/gui/include/gui/TransactionState.h b/libs/gui/include/gui/TransactionState.h new file mode 100644 index 0000000000..4358227dae --- /dev/null +++ b/libs/gui/include/gui/TransactionState.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 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/gui/FrameTimelineInfo.h> +#include <binder/Parcelable.h> +#include <gui/LayerState.h> + +namespace android { + +// Class to store all the transaction data and the parcelling logic +class TransactionState { +public: + explicit TransactionState() = default; + TransactionState(TransactionState const& other) = default; + status_t writeToParcel(Parcel* parcel) const; + status_t readFromParcel(const Parcel* parcel); + layer_state_t* getLayerState(const sp<SurfaceControl>& sc); + DisplayState& getDisplayState(const sp<IBinder>& token); + + // Returns the current id of the transaction. + // The id is updated every time the transaction is applied. + uint64_t getId() const { return mId; } + std::vector<uint64_t> getMergedTransactionIds() const { return mMergedTransactionIds; } + void enableDebugLogCallPoints() { mLogCallPoints = true; } + void merge(TransactionState&& other, + const std::function<void(layer_state_t&)>& onBufferOverwrite); + + // copied from FrameTimelineInfo::merge() + void mergeFrameTimelineInfo(const FrameTimelineInfo& other); + void clear(); + bool operator==(const TransactionState& rhs) const = default; + bool operator!=(const TransactionState& rhs) const = default; + + uint64_t mId = 0; + std::vector<uint64_t> mMergedTransactionIds; + uint32_t mFlags = 0; + // The vsync id provided by Choreographer.getVsyncId and the input event id + gui::FrameTimelineInfo mFrameTimelineInfo; + // mDesiredPresentTime is the time in nanoseconds that the client would like the transaction + // to be presented. When it is not possible to present at exactly that time, it will be + // presented after the time has passed. + // + // If the client didn't pass a desired presentation time, mDesiredPresentTime will be + // populated to the time setBuffer was called, and mIsAutoTimestamp will be set to true. + // + // Desired present times that are more than 1 second in the future may be ignored. + // When a desired present time has already passed, the transaction will be presented as soon + // as possible. + // + // Transactions from the same process are presented in the same order that they are applied. + // The desired present time does not affect this ordering. + int64_t mDesiredPresentTime = 0; + bool mIsAutoTimestamp = true; + // If not null, transactions will be queued up using this token otherwise a common token + // per process will be used. + sp<IBinder> mApplyToken; + // Indicates that the Transaction may contain buffers that should be cached. The reason this + // is only a guess is that buffers can be removed before cache is called. This is only a + // hint that at some point a buffer was added to this transaction before apply was called. + bool mMayContainBuffer = false; + // Prints debug logs when enabled. + bool mLogCallPoints = false; + + std::vector<DisplayState> mDisplayStates; + std::vector<ComposerState> mComposerStates; + InputWindowCommands mInputWindowCommands; + std::vector<client_cache_t> mUncacheBuffers; + // Note: mHasListenerCallbacks can be true even if mListenerCallbacks is + // empty. + bool mHasListenerCallbacks = false; + std::vector<ListenerCallbacks> mListenerCallbacks; + +private: + // We keep track of the last MAX_MERGE_HISTORY_LENGTH merged transaction ids. + // Ordered most recently merged to least recently merged. + static constexpr size_t MAX_MERGE_HISTORY_LENGTH = 10u; +}; + +}; // namespace android diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp index 87051a7aac..e20345dd1a 100644 --- a/libs/gui/tests/Android.bp +++ b/libs/gui/tests/Android.bp @@ -90,6 +90,7 @@ cc_test { "testserver/TestServerClient.cpp", "testserver/TestServerHost.cpp", "TextureRenderer.cpp", + "TransactionState_test.cpp", "VsyncEventData_test.cpp", "WindowInfo_test.cpp", ], diff --git a/libs/gui/tests/TransactionState_test.cpp b/libs/gui/tests/TransactionState_test.cpp new file mode 100644 index 0000000000..179b264dbf --- /dev/null +++ b/libs/gui/tests/TransactionState_test.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2025 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 <gmock/gmock.h> + +#include <gtest/gtest.h> +#include <unordered_map> +#include "android/gui/FocusRequest.h" +#include "binder/Binder.h" +#include "binder/Parcel.h" +#include "gtest/gtest.h" +#include "gui/LayerState.h" +#include "gui/WindowInfo.h" + +#include "gui/TransactionState.h" + +namespace android { + +void sprintf(std::string& out, const char* format, ...) { + va_list arg_list; + va_start(arg_list, format); + + int len = vsnprintf(nullptr, 0, format, arg_list); + if (len < 0) { + va_end(arg_list); + } + std::string line(len, '\0'); + int written = vsnprintf(line.data(), len + 1, format, arg_list); + if (written != len) { + va_end(arg_list); + } + line.pop_back(); + out += line; + va_end(arg_list); +} + +constexpr std::string dump_struct(auto& x) { + std::string s; +#if __has_builtin(__builtin_dump_struct) + __builtin_dump_struct(&x, sprintf, s); +#else + (void)x; +#endif + return s; +} + +void PrintTo(const TransactionState& state, ::std::ostream* os) { + *os << dump_struct(state); + *os << state.mFrameTimelineInfo.toString(); + for (auto mergedId : state.mMergedTransactionIds) { + *os << mergedId << ","; + } +} + +void PrintTo(const ComposerState& state, ::std::ostream* os) { + *os << dump_struct(state.state); + *os << state.state.getWindowInfo(); +} + +// In case EXPECT_EQ fails, this function is useful to pinpoint exactly which +// field did not compare ==. +void Compare(const TransactionState& s1, const TransactionState& s2) { + EXPECT_EQ(s1.mId, s2.mId); + EXPECT_EQ(s1.mMergedTransactionIds, s2.mMergedTransactionIds); + EXPECT_EQ(s1.mFlags, s2.mFlags); + EXPECT_EQ(s1.mFrameTimelineInfo, s2.mFrameTimelineInfo); + EXPECT_EQ(s1.mDesiredPresentTime, s2.mDesiredPresentTime); + EXPECT_EQ(s1.mIsAutoTimestamp, s2.mIsAutoTimestamp); + EXPECT_EQ(s1.mApplyToken, s2.mApplyToken); + EXPECT_EQ(s1.mMayContainBuffer, s2.mMayContainBuffer); + EXPECT_EQ(s1.mLogCallPoints, s2.mLogCallPoints); + EXPECT_EQ(s1.mDisplayStates.size(), s2.mDisplayStates.size()); + EXPECT_THAT(s1.mDisplayStates, ::testing::ContainerEq(s2.mDisplayStates)); + EXPECT_EQ(s1.mComposerStates.size(), s2.mComposerStates.size()); + EXPECT_EQ(s1.mComposerStates, s2.mComposerStates); + EXPECT_EQ(s1.mInputWindowCommands, s2.mInputWindowCommands); + EXPECT_EQ(s1.mUncacheBuffers, s2.mUncacheBuffers); + EXPECT_EQ(s1.mHasListenerCallbacks, s2.mHasListenerCallbacks); + EXPECT_EQ(s1.mListenerCallbacks.size(), s2.mListenerCallbacks.size()); + EXPECT_EQ(s1.mListenerCallbacks, s2.mListenerCallbacks); +} + +std::unique_ptr<std::unordered_map<int, sp<BBinder>>> createTokenMap(size_t maxSize) { + auto result = std::make_unique<std::unordered_map<int, sp<BBinder>>>(); + for (size_t i = 0; i < maxSize; ++i) { + result->emplace(i, sp<BBinder>::make()); + } + return result; +} + +constexpr size_t kMaxComposerStates = 2; +ComposerState createComposerStateForTest(size_t i) { + static const auto* const sLayerHandle = createTokenMap(kMaxComposerStates).release(); + + ComposerState state; + state.state.what = layer_state_t::eFlagsChanged; + state.state.surface = sLayerHandle->at(i); + state.state.layerId = i; + state.state.flags = 20 * i; + return state; +} + +constexpr size_t kMaxDisplayStates = 5; +DisplayState createDisplayStateForTest(size_t i) { + static const auto* const sDisplayTokens = createTokenMap(kMaxDisplayStates).release(); + + DisplayState displayState; + displayState.what = DisplayState::eFlagsChanged; + displayState.token = sDisplayTokens->at(i); + displayState.flags = 20 * i; + return displayState; +} + +TransactionState createTransactionStateForTest() { + static sp<BBinder> sApplyToken = sp<BBinder>::make(); + + TransactionState state; + state.mId = 123; + state.mMergedTransactionIds.push_back(15); + state.mMergedTransactionIds.push_back(0); + state.mFrameTimelineInfo.vsyncId = 14; + state.mDesiredPresentTime = 11; + state.mIsAutoTimestamp = true; + state.mApplyToken = sApplyToken; + for (size_t i = 0; i < kMaxDisplayStates; i++) { + state.mDisplayStates.push_back(createDisplayStateForTest(i)); + } + for (size_t i = 0; i < kMaxComposerStates; i++) { + state.mComposerStates.push_back(createComposerStateForTest(i)); + } + static const auto* const sFocusRequestTokens = createTokenMap(5).release(); + for (int i = 0; i < 5; i++) { + gui::FocusRequest request; + request.token = sFocusRequestTokens->at(i); + request.timestamp = i; + state.mInputWindowCommands.addFocusRequest(request); + } + static const auto* const sCacheToken = createTokenMap(5).release(); + for (int i = 0; i < 5; i++) { + client_cache_t cache; + cache.token = sCacheToken->at(i); + cache.id = i; + state.mUncacheBuffers.emplace_back(std::move(cache)); + } + static const auto* const sListenerCallbacks = []() { + auto* callbacks = new std::vector<ListenerCallbacks>(); + for (int i = 0; i < 5; i++) { + callbacks->emplace_back(sp<BBinder>::make(), + std::unordered_set<CallbackId, CallbackIdHash>{}); + } + return callbacks; + }(); + state.mHasListenerCallbacks = true; + state.mListenerCallbacks = *sListenerCallbacks; + return state; +} + +TransactionState createEmptyTransaction(uint64_t id) { + TransactionState state; + state.mId = id; + return state; +} + +TEST(TransactionStateTest, parcel) { + TransactionState state = createTransactionStateForTest(); + Parcel p; + state.writeToParcel(&p); + p.setDataPosition(0); + TransactionState parcelledState; + parcelledState.readFromParcel(&p); + EXPECT_EQ(state, parcelledState); +}; + +TEST(TransactionStateTest, parcelDisplayState) { + DisplayState state = createDisplayStateForTest(0); + Parcel p; + state.write(p); + p.setDataPosition(0); + DisplayState parcelledState; + parcelledState.read(p); + EXPECT_EQ(state, parcelledState); +}; + +TEST(TransactionStateTest, parcelLayerState) { + ComposerState state = createComposerStateForTest(0); + Parcel p; + state.write(p); + p.setDataPosition(0); + ComposerState parcelledState; + parcelledState.read(p); + EXPECT_EQ(state, parcelledState); +}; + +TEST(TransactionStateTest, parcelEmptyState) { + TransactionState state; + Parcel p; + state.writeToParcel(&p); + p.setDataPosition(0); + TransactionState parcelledState; + state.readFromParcel(&p); + EXPECT_EQ(state, parcelledState); +}; + +TEST(TransactionStateTest, mergeLayerState) { + ComposerState composerState = createComposerStateForTest(0); + ComposerState update; + update.state.surface = composerState.state.surface; + update.state.layerId = 0; + update.state.what = layer_state_t::eAlphaChanged; + update.state.color.a = .42; + composerState.state.merge(update.state); + + ComposerState expectedMergedState = createComposerStateForTest(0); + expectedMergedState.state.what |= layer_state_t::eAlphaChanged; + expectedMergedState.state.color.a = .42; + EXPECT_EQ(composerState, expectedMergedState); +}; + +TEST(TransactionStateTest, merge) { + // Setup. + static constexpr uint64_t kUpdateTransactionId = 200; + + TransactionState state = createTransactionStateForTest(); + + TransactionState update; + update.mId = kUpdateTransactionId; + { + ComposerState composerState; + composerState.state.surface = state.mComposerStates[0].state.surface; + composerState.state.what = layer_state_t::eAlphaChanged; + composerState.state.color.a = .42; + update.mComposerStates.push_back(composerState); + } + { + ComposerState composerState; + composerState.state.surface = state.mComposerStates[1].state.surface; + composerState.state.what = layer_state_t::eBufferChanged; + update.mComposerStates.push_back(composerState); + } + int32_t overrwiteLayerId = -1; + // Mutation. + state.merge(std::move(update), + [&overrwiteLayerId](layer_state_t ls) { overrwiteLayerId = ls.layerId; }); + // Assertions. + EXPECT_EQ(1, overrwiteLayerId); + EXPECT_EQ(update, createEmptyTransaction(update.getId())); + + TransactionState expectedMergedState = createTransactionStateForTest(); + expectedMergedState.mMergedTransactionIds + .insert(expectedMergedState.mMergedTransactionIds.begin(), kUpdateTransactionId); + expectedMergedState.mComposerStates.at(0).state.what |= layer_state_t::eAlphaChanged; + expectedMergedState.mComposerStates.at(0).state.color.a = .42; + expectedMergedState.mComposerStates.at(1).state.what |= layer_state_t::eBufferChanged; + auto inputCommands = expectedMergedState.mInputWindowCommands; + + // desired present time is not merged. + expectedMergedState.mDesiredPresentTime = state.mDesiredPresentTime; + + EXPECT_EQ(state.mComposerStates[0], expectedMergedState.mComposerStates[0]); + EXPECT_EQ(state.mInputWindowCommands, expectedMergedState.mInputWindowCommands); + EXPECT_EQ(state, expectedMergedState); +}; + +TEST(TransactionStateTest, clear) { + TransactionState state = createTransactionStateForTest(); + state.clear(); + TransactionState emptyState = createEmptyTransaction(state.getId()); + EXPECT_EQ(state, emptyState); +}; + +} // namespace android |