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

Commit 15bfd3dd authored by Anton Ivanov's avatar Anton Ivanov
Browse files

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
parent 2f706495
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -281,6 +281,7 @@ filegroup {
        "SurfaceControl.cpp",
        "SurfaceComposerClient.cpp",
        "SyncFeatures.cpp",
        "TransactionState.cpp",
        "VsyncEventData.cpp",
        "view/Surface.cpp",
        "WindowInfosListenerReporter.cpp",
+263 −0
Original line number Diff line number Diff line
/*
 * 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
+94 −0
Original line number Diff line number Diff line
/*
 * 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
+1 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ cc_test {
        "testserver/TestServerClient.cpp",
        "testserver/TestServerHost.cpp",
        "TextureRenderer.cpp",
        "TransactionState_test.cpp",
        "VsyncEventData_test.cpp",
        "WindowInfo_test.cpp",
    ],
+284 −0
Original line number Diff line number Diff line
/*
 * 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