/*
* Copyright 2016 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/FrameTimestamps.h>

#define LOG_TAG "FrameEvents"

#include <cutils/compiler.h>  // For CC_[UN]LIKELY
#include <inttypes.h>
#include <utils/Log.h>
#include <utils/String8.h>

#include <algorithm>
#include <limits>
#include <numeric>

namespace android {


// ============================================================================
// FrameEvents
// ============================================================================

bool FrameEvents::hasPostedInfo() const {
    return FrameEvents::isValidTimestamp(postedTime);
}

bool FrameEvents::hasRequestedPresentInfo() const {
    return FrameEvents::isValidTimestamp(requestedPresentTime);
}

bool FrameEvents::hasLatchInfo() const {
    return FrameEvents::isValidTimestamp(latchTime);
}

bool FrameEvents::hasFirstRefreshStartInfo() const {
    return FrameEvents::isValidTimestamp(firstRefreshStartTime);
}

bool FrameEvents::hasLastRefreshStartInfo() const {
    // The last refresh start time may continue to update until a new frame
    // is latched. We know we have the final value once the release info is set.
    return addReleaseCalled;
}

bool FrameEvents::hasDequeueReadyInfo() const {
    return FrameEvents::isValidTimestamp(dequeueReadyTime);
}

bool FrameEvents::hasAcquireInfo() const {
    return acquireFence->isValid();
}

bool FrameEvents::hasGpuCompositionDoneInfo() const {
    // We may not get a gpuCompositionDone in addPostComposite if
    // client/gles compositing isn't needed.
    return addPostCompositeCalled;
}

bool FrameEvents::hasDisplayPresentInfo() const {
    // We may not get a displayPresent in addPostComposite for HWC1.
    return addPostCompositeCalled;
}

bool FrameEvents::hasReleaseInfo() const {
    return addReleaseCalled;
}

void FrameEvents::checkFencesForCompletion() {
    acquireFence->getSignalTime();
    gpuCompositionDoneFence->getSignalTime();
    displayPresentFence->getSignalTime();
    releaseFence->getSignalTime();
}

static void dumpFenceTime(String8& outString, const char* name,
        bool pending, const FenceTime& fenceTime) {
    outString.appendFormat("--- %s", name);
    nsecs_t signalTime = fenceTime.getCachedSignalTime();
    if (Fence::isValidTimestamp(signalTime)) {
        outString.appendFormat("%" PRId64 "\n", signalTime);
    } else if (pending || signalTime == Fence::SIGNAL_TIME_PENDING) {
        outString.appendFormat("Pending\n");
    } else if (&fenceTime == FenceTime::NO_FENCE.get()){
        outString.appendFormat("N/A\n");
    } else {
        outString.appendFormat("Error\n");
    }
}

void FrameEvents::dump(String8& outString) const
{
    if (!valid) {
        return;
    }

    outString.appendFormat("-- Frame %" PRIu64 "\n", frameNumber);
    outString.appendFormat("--- Posted      \t%" PRId64 "\n", postedTime);
    outString.appendFormat("--- Req. Present\t%" PRId64 "\n", requestedPresentTime);

    outString.appendFormat("--- Latched     \t");
    if (FrameEvents::isValidTimestamp(latchTime)) {
        outString.appendFormat("%" PRId64 "\n", latchTime);
    } else {
        outString.appendFormat("Pending\n");
    }

    outString.appendFormat("--- Refresh (First)\t");
    if (FrameEvents::isValidTimestamp(firstRefreshStartTime)) {
        outString.appendFormat("%" PRId64 "\n", firstRefreshStartTime);
    } else {
        outString.appendFormat("Pending\n");
    }

    outString.appendFormat("--- Refresh (Last)\t");
    if (FrameEvents::isValidTimestamp(lastRefreshStartTime)) {
        outString.appendFormat("%" PRId64 "\n", lastRefreshStartTime);
    } else {
        outString.appendFormat("Pending\n");
    }

    dumpFenceTime(outString, "Acquire           \t",
            true, *acquireFence);
    dumpFenceTime(outString, "GPU Composite Done\t",
            !addPostCompositeCalled, *gpuCompositionDoneFence);
    dumpFenceTime(outString, "Display Present   \t",
            !addPostCompositeCalled, *displayPresentFence);

    outString.appendFormat("--- DequeueReady  \t");
    if (FrameEvents::isValidTimestamp(dequeueReadyTime)) {
        outString.appendFormat("%" PRId64 "\n", dequeueReadyTime);
    } else {
        outString.appendFormat("Pending\n");
    }

    dumpFenceTime(outString, "Release           \t",
            true, *releaseFence);
}


// ============================================================================
// FrameEventHistory
// ============================================================================

namespace {

struct FrameNumberEqual {
    FrameNumberEqual(uint64_t frameNumber) : mFrameNumber(frameNumber) {}
    bool operator()(const FrameEvents& frame) {
        return frame.valid && mFrameNumber == frame.frameNumber;
    }
    const uint64_t mFrameNumber;
};

}  // namespace

FrameEventHistory::~FrameEventHistory() = default;

FrameEvents* FrameEventHistory::getFrame(uint64_t frameNumber) {
    auto frame = std::find_if(
            mFrames.begin(), mFrames.end(), FrameNumberEqual(frameNumber));
    return frame == mFrames.end() ? nullptr : &(*frame);
}

FrameEvents* FrameEventHistory::getFrame(uint64_t frameNumber, size_t* iHint) {
    *iHint = std::min(*iHint, mFrames.size());
    auto hint = mFrames.begin() + *iHint;
    auto frame = std::find_if(
            hint, mFrames.end(), FrameNumberEqual(frameNumber));
    if (frame == mFrames.end()) {
        frame = std::find_if(
                mFrames.begin(), hint, FrameNumberEqual(frameNumber));
        if (frame == hint) {
            return nullptr;
        }
    }
    *iHint = static_cast<size_t>(std::distance(mFrames.begin(), frame));
    return &(*frame);
}

void FrameEventHistory::checkFencesForCompletion() {
    for (auto& frame : mFrames) {
        frame.checkFencesForCompletion();
    }
}

// Uses !|valid| as the MSB.
static bool FrameNumberLessThan(
        const FrameEvents& lhs, const FrameEvents& rhs) {
    if (lhs.valid == rhs.valid) {
        return lhs.frameNumber < rhs.frameNumber;
    }
    return lhs.valid;
}

void FrameEventHistory::dump(String8& outString) const {
    auto earliestFrame = std::min_element(
            mFrames.begin(), mFrames.end(), &FrameNumberLessThan);
    if (!earliestFrame->valid) {
        outString.appendFormat("-- N/A\n");
        return;
    }
    for (auto frame = earliestFrame; frame != mFrames.end(); ++frame) {
        frame->dump(outString);
    }
    for (auto frame = mFrames.begin(); frame != earliestFrame; ++frame) {
        frame->dump(outString);
    }
}


// ============================================================================
// ProducerFrameEventHistory
// ============================================================================

ProducerFrameEventHistory::~ProducerFrameEventHistory() = default;

nsecs_t ProducerFrameEventHistory::snapToNextTick(
        nsecs_t timestamp, nsecs_t tickPhase, nsecs_t tickInterval) {
    nsecs_t tickOffset = (tickPhase - timestamp) % tickInterval;
    // Integer modulo rounds towards 0 and not -inf before taking the remainder,
    // so adjust the offset if it is negative.
    if (tickOffset < 0) {
        tickOffset += tickInterval;
    }
    return timestamp + tickOffset;
}

nsecs_t ProducerFrameEventHistory::getNextCompositeDeadline(
        const nsecs_t now) const{
    return snapToNextTick(
            now, mCompositorTiming.deadline, mCompositorTiming.interval);
}

void ProducerFrameEventHistory::updateAcquireFence(
        uint64_t frameNumber, std::shared_ptr<FenceTime>&& acquire) {
    FrameEvents* frame = getFrame(frameNumber, &mAcquireOffset);
    if (frame == nullptr) {
        ALOGE("updateAcquireFence: Did not find frame.");
        return;
    }

    if (acquire->isValid()) {
        mAcquireTimeline.push(acquire);
        frame->acquireFence = std::move(acquire);
    } else {
        // If there isn't an acquire fence, assume that buffer was
        // ready for the consumer when posted.
        frame->acquireFence = std::make_shared<FenceTime>(frame->postedTime);
    }
}

void ProducerFrameEventHistory::applyDelta(
        const FrameEventHistoryDelta& delta) {
    mCompositorTiming = delta.mCompositorTiming;

    for (auto& d : delta.mDeltas) {
        // Avoid out-of-bounds access.
        if (CC_UNLIKELY(d.mIndex >= mFrames.size())) {
            ALOGE("applyDelta: Bad index.");
            return;
        }

        FrameEvents& frame = mFrames[d.mIndex];

        frame.addPostCompositeCalled = d.mAddPostCompositeCalled != 0;
        frame.addReleaseCalled = d.mAddReleaseCalled != 0;

        frame.postedTime = d.mPostedTime;
        frame.requestedPresentTime = d.mRequestedPresentTime;
        frame.latchTime = d.mLatchTime;
        frame.firstRefreshStartTime = d.mFirstRefreshStartTime;
        frame.lastRefreshStartTime = d.mLastRefreshStartTime;
        frame.dequeueReadyTime = d.mDequeueReadyTime;

        if (frame.frameNumber != d.mFrameNumber) {
            // We got a new frame. Initialize some of the fields.
            frame.frameNumber = d.mFrameNumber;
            frame.acquireFence = FenceTime::NO_FENCE;
            frame.gpuCompositionDoneFence = FenceTime::NO_FENCE;
            frame.displayPresentFence = FenceTime::NO_FENCE;
            frame.releaseFence = FenceTime::NO_FENCE;
            // The consumer only sends valid frames.
            frame.valid = true;
        }

        applyFenceDelta(&mGpuCompositionDoneTimeline,
                &frame.gpuCompositionDoneFence, d.mGpuCompositionDoneFence);
        applyFenceDelta(&mPresentTimeline,
                &frame.displayPresentFence, d.mDisplayPresentFence);
        applyFenceDelta(&mReleaseTimeline,
                &frame.releaseFence, d.mReleaseFence);
    }
}

void ProducerFrameEventHistory::updateSignalTimes() {
    mAcquireTimeline.updateSignalTimes();
    mGpuCompositionDoneTimeline.updateSignalTimes();
    mPresentTimeline.updateSignalTimes();
    mReleaseTimeline.updateSignalTimes();
}

void ProducerFrameEventHistory::applyFenceDelta(FenceTimeline* timeline,
        std::shared_ptr<FenceTime>* dst, const FenceTime::Snapshot& src) const {
    if (CC_UNLIKELY(dst == nullptr || dst->get() == nullptr)) {
        ALOGE("applyFenceDelta: dst is null.");
        return;
    }

    switch (src.state) {
        case FenceTime::Snapshot::State::EMPTY:
            return;
        case FenceTime::Snapshot::State::FENCE:
            ALOGE_IF((*dst)->isValid(), "applyFenceDelta: Unexpected fence.");
            *dst = createFenceTime(src.fence);
            timeline->push(*dst);
            return;
        case FenceTime::Snapshot::State::SIGNAL_TIME:
            if ((*dst)->isValid()) {
                (*dst)->applyTrustedSnapshot(src);
            } else {
                *dst = std::make_shared<FenceTime>(src.signalTime);
            }
            return;
    }
}

std::shared_ptr<FenceTime> ProducerFrameEventHistory::createFenceTime(
        const sp<Fence>& fence) const {
    return std::make_shared<FenceTime>(fence);
}


// ============================================================================
// ConsumerFrameEventHistory
// ============================================================================

ConsumerFrameEventHistory::~ConsumerFrameEventHistory() = default;

void ConsumerFrameEventHistory::onDisconnect() {
    mCurrentConnectId++;
    mProducerWantsEvents = false;
}

void ConsumerFrameEventHistory::initializeCompositorTiming(
        const CompositorTiming& compositorTiming) {
    mCompositorTiming = compositorTiming;
}

void ConsumerFrameEventHistory::addQueue(const NewFrameEventsEntry& newEntry) {
    // Overwrite all fields of the frame with default values unless set here.
    FrameEvents newTimestamps;
    newTimestamps.connectId = mCurrentConnectId;
    newTimestamps.frameNumber = newEntry.frameNumber;
    newTimestamps.postedTime = newEntry.postedTime;
    newTimestamps.requestedPresentTime = newEntry.requestedPresentTime;
    newTimestamps.acquireFence = newEntry.acquireFence;
    newTimestamps.valid = true;
    mFrames[mQueueOffset] = newTimestamps;

    // Note: We avoid sending the acquire fence back to the caller since
    // they have the original one already, so there is no need to set the
    // acquire dirty bit.
    mFramesDirty[mQueueOffset].setDirty<FrameEvent::POSTED>();

    mQueueOffset = (mQueueOffset + 1) % mFrames.size();
}

void ConsumerFrameEventHistory::addLatch(
        uint64_t frameNumber, nsecs_t latchTime) {
    FrameEvents* frame = getFrame(frameNumber, &mCompositionOffset);
    if (frame == nullptr) {
        ALOGE_IF(mProducerWantsEvents, "addLatch: Did not find frame.");
        return;
    }
    frame->latchTime = latchTime;
    mFramesDirty[mCompositionOffset].setDirty<FrameEvent::LATCH>();
}

void ConsumerFrameEventHistory::addPreComposition(
        uint64_t frameNumber, nsecs_t refreshStartTime) {
    FrameEvents* frame = getFrame(frameNumber, &mCompositionOffset);
    if (frame == nullptr) {
        ALOGE_IF(mProducerWantsEvents,
                "addPreComposition: Did not find frame.");
        return;
    }
    frame->lastRefreshStartTime = refreshStartTime;
    mFramesDirty[mCompositionOffset].setDirty<FrameEvent::LAST_REFRESH_START>();
    if (!FrameEvents::isValidTimestamp(frame->firstRefreshStartTime)) {
        frame->firstRefreshStartTime = refreshStartTime;
        mFramesDirty[mCompositionOffset].setDirty<FrameEvent::FIRST_REFRESH_START>();
    }
}

void ConsumerFrameEventHistory::addPostComposition(uint64_t frameNumber,
        const std::shared_ptr<FenceTime>& gpuCompositionDone,
        const std::shared_ptr<FenceTime>& displayPresent,
        const CompositorTiming& compositorTiming) {
    mCompositorTiming = compositorTiming;

    FrameEvents* frame = getFrame(frameNumber, &mCompositionOffset);
    if (frame == nullptr) {
        ALOGE_IF(mProducerWantsEvents,
                "addPostComposition: Did not find frame.");
        return;
    }
    // Only get GPU and present info for the first composite.
    if (!frame->addPostCompositeCalled) {
        frame->addPostCompositeCalled = true;
        frame->gpuCompositionDoneFence = gpuCompositionDone;
        mFramesDirty[mCompositionOffset].setDirty<FrameEvent::GPU_COMPOSITION_DONE>();
        if (!frame->displayPresentFence->isValid()) {
            frame->displayPresentFence = displayPresent;
            mFramesDirty[mCompositionOffset].setDirty<FrameEvent::DISPLAY_PRESENT>();
        }
    }
}

void ConsumerFrameEventHistory::addRelease(uint64_t frameNumber,
        nsecs_t dequeueReadyTime, std::shared_ptr<FenceTime>&& release) {
    FrameEvents* frame = getFrame(frameNumber, &mReleaseOffset);
    if (frame == nullptr) {
        ALOGE_IF(mProducerWantsEvents, "addRelease: Did not find frame.");
        return;
    }
    frame->addReleaseCalled = true;
    frame->dequeueReadyTime = dequeueReadyTime;
    frame->releaseFence = std::move(release);
    mFramesDirty[mReleaseOffset].setDirty<FrameEvent::RELEASE>();
}

void ConsumerFrameEventHistory::getFrameDelta(
        FrameEventHistoryDelta* delta,
        const std::array<FrameEvents, MAX_FRAME_HISTORY>::iterator& frame) {
    mProducerWantsEvents = true;
    size_t i = static_cast<size_t>(std::distance(mFrames.begin(), frame));
    if (mFramesDirty[i].anyDirty()) {
        // Make sure only to send back deltas for the current connection
        // since the producer won't have the correct state to apply a delta
        // from a previous connection.
        if (mFrames[i].connectId == mCurrentConnectId) {
            delta->mDeltas.emplace_back(i, *frame, mFramesDirty[i]);
        }
        mFramesDirty[i].reset();
    }
}

void ConsumerFrameEventHistory::getAndResetDelta(
        FrameEventHistoryDelta* delta) {
    mProducerWantsEvents = true;
    delta->mCompositorTiming = mCompositorTiming;

    // Write these in order of frame number so that it is easy to
    // add them to a FenceTimeline in the proper order producer side.
    delta->mDeltas.reserve(mFramesDirty.size());
    auto earliestFrame = std::min_element(
            mFrames.begin(), mFrames.end(), &FrameNumberLessThan);
    for (auto frame = earliestFrame; frame != mFrames.end(); ++frame) {
        getFrameDelta(delta, frame);
    }
    for (auto frame = mFrames.begin(); frame != earliestFrame; ++frame) {
        getFrameDelta(delta, frame);
    }
}


// ============================================================================
// FrameEventsDelta
// ============================================================================

FrameEventsDelta::FrameEventsDelta(
        size_t index,
        const FrameEvents& frameTimestamps,
        const FrameEventDirtyFields& dirtyFields)
    : mIndex(index),
      mFrameNumber(frameTimestamps.frameNumber),
      mAddPostCompositeCalled(frameTimestamps.addPostCompositeCalled),
      mAddReleaseCalled(frameTimestamps.addReleaseCalled),
      mPostedTime(frameTimestamps.postedTime),
      mRequestedPresentTime(frameTimestamps.requestedPresentTime),
      mLatchTime(frameTimestamps.latchTime),
      mFirstRefreshStartTime(frameTimestamps.firstRefreshStartTime),
      mLastRefreshStartTime(frameTimestamps.lastRefreshStartTime),
      mDequeueReadyTime(frameTimestamps.dequeueReadyTime) {
    if (dirtyFields.isDirty<FrameEvent::GPU_COMPOSITION_DONE>()) {
        mGpuCompositionDoneFence =
                frameTimestamps.gpuCompositionDoneFence->getSnapshot();
    }
    if (dirtyFields.isDirty<FrameEvent::DISPLAY_PRESENT>()) {
        mDisplayPresentFence =
                frameTimestamps.displayPresentFence->getSnapshot();
    }
    if (dirtyFields.isDirty<FrameEvent::RELEASE>()) {
        mReleaseFence = frameTimestamps.releaseFence->getSnapshot();
    }
}

constexpr size_t FrameEventsDelta::minFlattenedSize() {
    return sizeof(FrameEventsDelta::mFrameNumber) +
            sizeof(uint16_t) + // mIndex
            sizeof(uint8_t) + // mAddPostCompositeCalled
            sizeof(uint8_t) + // mAddReleaseCalled
            sizeof(FrameEventsDelta::mPostedTime) +
            sizeof(FrameEventsDelta::mRequestedPresentTime) +
            sizeof(FrameEventsDelta::mLatchTime) +
            sizeof(FrameEventsDelta::mFirstRefreshStartTime) +
            sizeof(FrameEventsDelta::mLastRefreshStartTime) +
            sizeof(FrameEventsDelta::mDequeueReadyTime);
}

// Flattenable implementation
size_t FrameEventsDelta::getFlattenedSize() const {
    auto fences = allFences(this);
    return minFlattenedSize() +
            std::accumulate(fences.begin(), fences.end(), size_t(0),
                    [](size_t a, const FenceTime::Snapshot* fence) {
                            return a + fence->getFlattenedSize();
                    });
}

size_t FrameEventsDelta::getFdCount() const {
    auto fences = allFences(this);
    return std::accumulate(fences.begin(), fences.end(), size_t(0),
            [](size_t a, const FenceTime::Snapshot* fence) {
                return a + fence->getFdCount();
            });
}

status_t FrameEventsDelta::flatten(void*& buffer, size_t& size, int*& fds,
            size_t& count) const {
    if (size < getFlattenedSize() || count < getFdCount()) {
        return NO_MEMORY;
    }

    if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY ||
            mIndex > std::numeric_limits<uint16_t>::max()) {
        return BAD_VALUE;
    }

    FlattenableUtils::write(buffer, size, mFrameNumber);

    // These are static_cast to uint16_t/uint8_t for alignment.
    FlattenableUtils::write(buffer, size, static_cast<uint16_t>(mIndex));
    FlattenableUtils::write(
            buffer, size, static_cast<uint8_t>(mAddPostCompositeCalled));
    FlattenableUtils::write(
            buffer, size, static_cast<uint8_t>(mAddReleaseCalled));

    FlattenableUtils::write(buffer, size, mPostedTime);
    FlattenableUtils::write(buffer, size, mRequestedPresentTime);
    FlattenableUtils::write(buffer, size, mLatchTime);
    FlattenableUtils::write(buffer, size, mFirstRefreshStartTime);
    FlattenableUtils::write(buffer, size, mLastRefreshStartTime);
    FlattenableUtils::write(buffer, size, mDequeueReadyTime);

    // Fences
    for (auto fence : allFences(this)) {
        status_t status = fence->flatten(buffer, size, fds, count);
        if (status != NO_ERROR) {
            return status;
        }
    }
    return NO_ERROR;
}

status_t FrameEventsDelta::unflatten(void const*& buffer, size_t& size,
            int const*& fds, size_t& count) {
    if (size < minFlattenedSize()) {
        return NO_MEMORY;
    }

    FlattenableUtils::read(buffer, size, mFrameNumber);

    // These were written as uint16_t/uint8_t for alignment.
    uint16_t temp16 = 0;
    FlattenableUtils::read(buffer, size, temp16);
    mIndex = temp16;
    if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY) {
        return BAD_VALUE;
    }
    uint8_t temp8 = 0;
    FlattenableUtils::read(buffer, size, temp8);
    mAddPostCompositeCalled = static_cast<bool>(temp8);
    FlattenableUtils::read(buffer, size, temp8);
    mAddReleaseCalled = static_cast<bool>(temp8);

    FlattenableUtils::read(buffer, size, mPostedTime);
    FlattenableUtils::read(buffer, size, mRequestedPresentTime);
    FlattenableUtils::read(buffer, size, mLatchTime);
    FlattenableUtils::read(buffer, size, mFirstRefreshStartTime);
    FlattenableUtils::read(buffer, size, mLastRefreshStartTime);
    FlattenableUtils::read(buffer, size, mDequeueReadyTime);

    // Fences
    for (auto fence : allFences(this)) {
        status_t status = fence->unflatten(buffer, size, fds, count);
        if (status != NO_ERROR) {
            return status;
        }
    }
    return NO_ERROR;
}


// ============================================================================
// FrameEventHistoryDelta
// ============================================================================

FrameEventHistoryDelta& FrameEventHistoryDelta::operator=(
        FrameEventHistoryDelta&& src) {
    mCompositorTiming = src.mCompositorTiming;

    if (CC_UNLIKELY(!mDeltas.empty())) {
        ALOGE("FrameEventHistoryDelta assign clobbering history.");
    }
    mDeltas = std::move(src.mDeltas);
    return *this;
}

constexpr size_t FrameEventHistoryDelta::minFlattenedSize() {
    return sizeof(uint32_t) + // mDeltas.size()
            sizeof(mCompositorTiming);
}

size_t FrameEventHistoryDelta::getFlattenedSize() const {
    return minFlattenedSize() +
            std::accumulate(mDeltas.begin(), mDeltas.end(), size_t(0),
                    [](size_t a, const FrameEventsDelta& delta) {
                            return a + delta.getFlattenedSize();
                    });
}

size_t FrameEventHistoryDelta::getFdCount() const {
    return std::accumulate(mDeltas.begin(), mDeltas.end(), size_t(0),
            [](size_t a, const FrameEventsDelta& delta) {
                    return a + delta.getFdCount();
            });
}

status_t FrameEventHistoryDelta::flatten(
            void*& buffer, size_t& size, int*& fds, size_t& count) const {
    if (mDeltas.size() > FrameEventHistory::MAX_FRAME_HISTORY) {
        return BAD_VALUE;
    }
    if (size < getFlattenedSize()) {
        return NO_MEMORY;
    }

    FlattenableUtils::write(buffer, size, mCompositorTiming);

    FlattenableUtils::write(
            buffer, size, static_cast<uint32_t>(mDeltas.size()));
    for (auto& d : mDeltas) {
        status_t status = d.flatten(buffer, size, fds, count);
        if (status != NO_ERROR) {
            return status;
        }
    }
    return NO_ERROR;
}

status_t FrameEventHistoryDelta::unflatten(
            void const*& buffer, size_t& size, int const*& fds, size_t& count) {
    if (size < minFlattenedSize()) {
        return NO_MEMORY;
    }

    FlattenableUtils::read(buffer, size, mCompositorTiming);

    uint32_t deltaCount = 0;
    FlattenableUtils::read(buffer, size, deltaCount);
    if (deltaCount > FrameEventHistory::MAX_FRAME_HISTORY) {
        return BAD_VALUE;
    }
    mDeltas.resize(deltaCount);
    for (auto& d : mDeltas) {
        status_t status = d.unflatten(buffer, size, fds, count);
        if (status != NO_ERROR) {
            return status;
        }
    }
    return NO_ERROR;
}


} // namespace android
