/*
 * Copyright (C) 2023 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_NDEBUG 0
#define LOG_TAG "GraphicsTracker_test"
#include <unistd.h>

#include <android/hardware_buffer.h>
#include <codec2/aidl/GraphicsTracker.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <gtest/gtest.h>
#include <gui/BufferQueue.h>
#include <gui/IProducerListener.h>
#include <gui/IConsumerListener.h>
#include <gui/Surface.h>
#include <private/android/AHardwareBufferHelpers.h>

#include <C2BlockInternal.h>
#include <C2FenceFactory.h>

#include <atomic>
#include <memory>
#include <iostream>
#include <thread>

using ::aidl::android::hardware::media::c2::implementation::GraphicsTracker;
using ::android::BufferItem;
using ::android::BufferQueue;
using ::android::Fence;
using ::android::GraphicBuffer;
using ::android::IGraphicBufferProducer;
using ::android::IGraphicBufferConsumer;
using ::android::IProducerListener;
using ::android::IConsumerListener;
using ::android::OK;
using ::android::sp;
using ::android::wp;

namespace {
struct BqStatistics {
    std::atomic<int> mDequeued;
    std::atomic<int> mQueued;
    std::atomic<int> mBlocked;
    std::atomic<int> mDropped;
    std::atomic<int> mDiscarded;
    std::atomic<int> mReleased;

    void log() {
        ALOGD("Dequeued: %d, Queued: %d, Blocked: %d, "
              "Dropped: %d, Discarded %d, Released %d",
              (int)mDequeued, (int)mQueued, (int)mBlocked,
              (int)mDropped, (int)mDiscarded, (int)mReleased);
    }

    void clear() {
        mDequeued = 0;
        mQueued = 0;
        mBlocked = 0;
        mDropped = 0;
        mDiscarded = 0;
        mReleased = 0;
    }
};

struct DummyConsumerListener : public android::BnConsumerListener {
    void onFrameAvailable(const BufferItem& /* item */) override {}
    void onBuffersReleased() override {}
    void onSidebandStreamChanged() override {}
};

struct TestConsumerListener : public android::BnConsumerListener {
    TestConsumerListener(const sp<IGraphicBufferConsumer> &consumer)
            : BnConsumerListener(), mConsumer(consumer) {}
    void onFrameAvailable(const BufferItem&) override {
        constexpr static int kRenderDelayUs = 1000000/30; // 30fps
        BufferItem buffer;
        // consume buffer
        sp<IGraphicBufferConsumer> consumer = mConsumer.promote();
        if (consumer != nullptr && consumer->acquireBuffer(&buffer, 0) == android::NO_ERROR) {
            ::usleep(kRenderDelayUs);
            consumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, buffer.mFence);
        }
    }
    void onBuffersReleased() override {}
    void onSidebandStreamChanged() override {}

    wp<IGraphicBufferConsumer> mConsumer;
};

struct TestProducerListener : public android::BnProducerListener {
    TestProducerListener(std::shared_ptr<GraphicsTracker> tracker,
                         std::shared_ptr<BqStatistics> &stat,
                         uint32_t generation) : BnProducerListener(),
        mTracker(tracker), mStat(stat), mGeneration(generation) {}
    virtual void onBufferReleased() override {
        auto tracker = mTracker.lock();
        if (tracker) {
            mStat->mReleased++;
            tracker->onReleased(mGeneration);
        }
    }
    virtual bool needsReleaseNotify() override { return true; }
    virtual void onBuffersDiscarded(const std::vector<int32_t>&) override {}

    std::weak_ptr<GraphicsTracker> mTracker;
    std::shared_ptr<BqStatistics> mStat;
    uint32_t mGeneration;
};

struct Frame {
    AHardwareBuffer *buffer_;
    sp<Fence> fence_;

    Frame() : buffer_{nullptr}, fence_{nullptr} {}
    Frame(AHardwareBuffer *buffer, sp<Fence> fence)
            : buffer_(buffer), fence_(fence) {}
    ~Frame() {
        if (buffer_) {
            AHardwareBuffer_release(buffer_);
        }
    }
};

struct FrameQueue {
    bool mStopped;
    bool mDrain;
    std::queue<std::shared_ptr<Frame>> mQueue;
    std::mutex mMutex;
    std::condition_variable mCond;

    FrameQueue() : mStopped{false}, mDrain{false} {}

    bool queueItem(AHardwareBuffer *buffer, sp<Fence> fence) {
        std::shared_ptr<Frame> frame = std::make_shared<Frame>(buffer, fence);
        if (mStopped) {
            return false;
        }
        if (!frame) {
            return false;
        }
        std::unique_lock<std::mutex> l(mMutex);
        mQueue.emplace(frame);
        l.unlock();
        mCond.notify_all();
        return true;
    }

    void stop(bool drain = false) {
        bool stopped = false;
        {
            std::unique_lock<std::mutex> l(mMutex);
            if (!mStopped) {
                mStopped = true;
                mDrain = drain;
                stopped = true;
            }
            l.unlock();
            if (stopped) {
                mCond.notify_all();
            }
        }
    }

    bool waitItem(std::shared_ptr<Frame> *frame) {
        while(true) {
            std::unique_lock<std::mutex> l(mMutex);
            if (!mDrain && mStopped) {
                // stop without consuming the queue.
                return false;
            }
            if (!mQueue.empty()) {
                *frame = mQueue.front();
                mQueue.pop();
                return true;
            } else if (mStopped) {
                // stop after consuming the queue.
                return false;
            }
            mCond.wait(l);
        }
    }
};

} // namespace anonymous

class GraphicsTrackerTest : public ::testing::Test {
public:
    const uint64_t kTestUsageFlag = GRALLOC_USAGE_SW_WRITE_OFTEN;

    void queueBuffer(FrameQueue *queue) {
        while (true) {
            std::shared_ptr<Frame> frame;
            if (!queue->waitItem(&frame)) {
                break;
            }
            uint64_t bid;
            if (__builtin_available(android __ANDROID_API_T__, *)) {
                if (AHardwareBuffer_getId(frame->buffer_, &bid) !=
                        android::NO_ERROR) {
                    break;
                }
            } else {
                break;
            }
            android::status_t ret = frame->fence_->wait(-1);
            if (ret != android::NO_ERROR) {
                mTracker->deallocate(bid, frame->fence_);
                mBqStat->mDiscarded++;
                continue;
            }

            std::shared_ptr<C2GraphicBlock> blk =
                    _C2BlockFactory::CreateGraphicBlock(frame->buffer_);
            if (!blk) {
                mTracker->deallocate(bid, Fence::NO_FENCE);
                mBqStat->mDiscarded++;
                continue;
            }
            IGraphicBufferProducer::QueueBufferInput input(
                    0, false,
                    HAL_DATASPACE_UNKNOWN, android::Rect(0, 0, 1, 1),
                    NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
            IGraphicBufferProducer::QueueBufferOutput output{};
            c2_status_t res = mTracker->render(
                    blk->share(C2Rect(1, 1), C2Fence()),
                    input, &output);
            if (res != C2_OK) {
                mTracker->deallocate(bid, Fence::NO_FENCE);
                mBqStat->mDiscarded++;
                continue;
            }
            if (output.bufferReplaced) {
                mBqStat->mDropped++;
            }
            mBqStat->mQueued++;
        }
    }

    void stopTrackerAfterUs(int us) {
        ::usleep(us);
        mTracker->stop();
    }

protected:
    bool init(int maxDequeueCount) {
        mTracker = GraphicsTracker::CreateGraphicsTracker(maxDequeueCount);
        if (!mTracker) {
            return false;
        }
        BufferQueue::createBufferQueue(&mProducer, &mConsumer);
        if (!mProducer || !mConsumer) {
            return false;
        }
        return true;
    }
    bool configure(sp<IProducerListener> producerListener,
                   sp<IConsumerListener> consumerListener,
                   int maxAcquiredCount = 1, bool controlledByApp = true) {
        if (mConsumer->consumerConnect(
                consumerListener, controlledByApp) != ::android::NO_ERROR) {
            return false;
        }
        if (mConsumer->setMaxAcquiredBufferCount(maxAcquiredCount) != ::android::NO_ERROR) {
            return false;
        }
        IGraphicBufferProducer::QueueBufferOutput qbo{};
        if (mProducer->connect(producerListener,
                          NATIVE_WINDOW_API_MEDIA, true, &qbo) != ::android::NO_ERROR) {
            return false;
        }
        if (mProducer->setDequeueTimeout(0) != ::android::NO_ERROR) {
            return false;
        }
        return true;
    }

    virtual void TearDown() override {
        mBqStat->log();
        mBqStat->clear();

        if (mTracker) {
            mTracker->stop();
            mTracker.reset();
        }
        if (mProducer) {
            mProducer->disconnect(NATIVE_WINDOW_API_MEDIA);
        }
        mProducer.clear();
        mConsumer.clear();
    }

protected:
    std::shared_ptr<BqStatistics> mBqStat = std::make_shared<BqStatistics>();
    sp<IGraphicBufferProducer> mProducer;
    sp<IGraphicBufferConsumer> mConsumer;
    std::shared_ptr<GraphicsTracker> mTracker;
};


TEST_F(GraphicsTrackerTest, AllocateAndBlockedTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 10;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new DummyConsumerListener()));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    c2_status_t ret = mTracker->configureGraphics(mProducer, generation);
    ASSERT_EQ(C2_OK, ret);
    ASSERT_EQ(maxDequeueCount, mTracker->getCurDequeueable());

    AHardwareBuffer *buf;
    sp<Fence> fence;
    uint64_t bid;

    // Allocate and check dequeueable
    if (__builtin_available(android __ANDROID_API_T__, *)) {
        for (int i = 0; i < maxDequeueCount; ++i) {
            ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf, &fence);
            ASSERT_EQ(C2_OK, ret);
            mBqStat->mDequeued++;
            ASSERT_EQ(maxDequeueCount - (i + 1), mTracker->getCurDequeueable());
            ASSERT_EQ(OK, AHardwareBuffer_getId(buf, &bid));
            ALOGD("alloced : bufferId: %llu", (unsigned long long)bid);
            AHardwareBuffer_release(buf);
        }
    } else {
        GTEST_SKIP();
    }

    // Allocate should be blocked
    ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf, &fence);
    ALOGD("alloc : err(%d, %d)", ret, C2_BLOCKING);
    ASSERT_EQ(C2_BLOCKING, ret);
    mBqStat->mBlocked++;
    ASSERT_EQ(0, mTracker->getCurDequeueable());
}

TEST_F(GraphicsTrackerTest, AllocateAndDeallocateTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 10;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new DummyConsumerListener()));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    c2_status_t ret = mTracker->configureGraphics(mProducer, generation);
    ASSERT_EQ(C2_OK, ret);

    ASSERT_EQ(maxDequeueCount, mTracker->getCurDequeueable());
    AHardwareBuffer *buf;
    sp<Fence> fence;
    uint64_t bid;
    std::vector<uint64_t> bids;

    // Allocate and store buffer id
    if (__builtin_available(android __ANDROID_API_T__, *)) {
        for (int i = 0; i < maxDequeueCount; ++i) {
            ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf, &fence);
            ASSERT_EQ(C2_OK, ret);
            mBqStat->mDequeued++;
            ASSERT_EQ(OK, AHardwareBuffer_getId(buf, &bid));
            bids.push_back(bid);
            ALOGD("alloced : bufferId: %llu", (unsigned long long)bid);
            AHardwareBuffer_release(buf);
        }
    } else {
        GTEST_SKIP();
    }

    // Deallocate and check dequeueable
    for (int i = 0; i < maxDequeueCount; ++i) {
        ALOGD("dealloc : bufferId: %llu", (unsigned long long)bids[i]);
        ret = mTracker->deallocate(bids[i], Fence::NO_FENCE);
        ASSERT_EQ(C2_OK, ret);
        ASSERT_EQ(i + 1, mTracker->getCurDequeueable());
        mBqStat->mDiscarded++;
    }
}

TEST_F(GraphicsTrackerTest, DropAndReleaseTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 10;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new DummyConsumerListener()));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    c2_status_t ret = mTracker->configureGraphics(mProducer, generation);
    ASSERT_EQ(C2_OK, ret);

    ASSERT_EQ(maxDequeueCount, mTracker->getCurDequeueable());

    FrameQueue frameQueue;
    std::thread queueThread(&GraphicsTrackerTest::queueBuffer, this, &frameQueue);
    AHardwareBuffer *buf1, *buf2;
    sp<Fence> fence1, fence2;

    ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf1, &fence1);
    ASSERT_EQ(C2_OK, ret);
    mBqStat->mDequeued++;
    ASSERT_EQ(maxDequeueCount - 1, mTracker->getCurDequeueable());

    ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf2, &fence2);
    ASSERT_EQ(C2_OK, ret);
    mBqStat->mDequeued++;
    ASSERT_EQ(maxDequeueCount - 2, mTracker->getCurDequeueable());

    // Queue two buffers without consuming, one should be dropped
    ASSERT_TRUE(frameQueue.queueItem(buf1, fence1));
    ASSERT_TRUE(frameQueue.queueItem(buf2, fence2));

    frameQueue.stop(true);
    if (queueThread.joinable()) {
        queueThread.join();
    }

    ASSERT_EQ(maxDequeueCount - 1, mTracker->getCurDequeueable());

    // Consume one buffer and release
    BufferItem item;
    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, item.mFence));
    // Nothing to consume
    ASSERT_NE(OK, mConsumer->acquireBuffer(&item, 0));

    ASSERT_EQ(maxDequeueCount, mTracker->getCurDequeueable());
    ASSERT_EQ(1, mBqStat->mReleased);
    ASSERT_EQ(1, mBqStat->mDropped);
}

TEST_F(GraphicsTrackerTest, RenderTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 10;
    const int maxNumAlloc = 20;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));

    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));

    int waitFd = -1;
    ASSERT_EQ(C2_OK, mTracker->getWaitableFd(&waitFd));
    C2Fence waitFence = _C2FenceFactory::CreatePipeFence(waitFd);


    FrameQueue frameQueue;
    std::thread queueThread(&GraphicsTrackerTest::queueBuffer, this, &frameQueue);

    int numAlloc = 0;

    while (numAlloc < maxNumAlloc) {
        AHardwareBuffer *buf;
        sp<Fence> fence;
        c2_status_t ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf, &fence);
        if (ret == C2_BLOCKING) {
            mBqStat->mBlocked++;
            c2_status_t waitRes = waitFence.wait(3000000000);
            if (waitRes == C2_TIMED_OUT || waitRes == C2_OK) {
                continue;
            }
            ALOGE("alloc wait failed: c2_err(%d)", waitRes);
            break;
        }
        if (ret != C2_OK) {
            ALOGE("alloc error: c2_err(%d)", ret);
            break;
        }
        mBqStat->mDequeued++;
        if (!frameQueue.queueItem(buf, fence)) {
            ALOGE("queue to render failed");
            break;
        }
        ++numAlloc;
    }

    frameQueue.stop(true);
    // Wait more than enough time(1 sec) to render all queued frames for sure.
    ::usleep(1000000);

    if (queueThread.joinable()) {
        queueThread.join();
    }
    ASSERT_EQ(numAlloc, maxNumAlloc);
    ASSERT_EQ(numAlloc, mBqStat->mDequeued);
    ASSERT_EQ(mBqStat->mDequeued, mBqStat->mQueued);
    ASSERT_EQ(mBqStat->mDequeued, mBqStat->mReleased + mBqStat->mDropped);
}

TEST_F(GraphicsTrackerTest, StopAndWaitTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 2;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));

    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));

    int waitFd = -1;
    ASSERT_EQ(C2_OK, mTracker->getWaitableFd(&waitFd));
    C2Fence waitFence = _C2FenceFactory::CreatePipeFence(waitFd);

    AHardwareBuffer *buf1, *buf2;
    sp<Fence> fence;

    ASSERT_EQ(C2_OK, mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf1, &fence));
    mBqStat->mDequeued++;
    AHardwareBuffer_release(buf1);

    ASSERT_EQ(C2_OK, mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf2, &fence));
    mBqStat->mDequeued++;
    AHardwareBuffer_release(buf2);

    ASSERT_EQ(0, mTracker->getCurDequeueable());
    ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(3000000000));

    std::thread stopThread(&GraphicsTrackerTest::stopTrackerAfterUs, this, 500000);
    ASSERT_EQ(C2_BAD_STATE, waitFence.wait(3000000000));

    if (stopThread.joinable()) {
        stopThread.join();
    }
}

TEST_F(GraphicsTrackerTest, SurfaceChangeTest) {
    uint32_t generation = 1;
    const int maxDequeueCount = 10;

    const int maxNumAlloc = 20;

    const int firstPassAlloc = 12;
    const int firstPassRender = 8;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));

    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));

    int waitFd = -1;
    ASSERT_EQ(C2_OK, mTracker->getWaitableFd(&waitFd));
    C2Fence waitFence = _C2FenceFactory::CreatePipeFence(waitFd);

    AHardwareBuffer *bufs[maxNumAlloc];
    sp<Fence> fences[maxNumAlloc];

    FrameQueue frameQueue;
    std::thread queueThread(&GraphicsTrackerTest::queueBuffer, this, &frameQueue);
    int numAlloc = 0;

    for (int i = 0; i < firstPassRender; ++i) {
        ASSERT_EQ(C2_OK, mTracker->allocate(
                0, 0, 0, kTestUsageFlag, &bufs[i], &fences[i]));
        mBqStat->mDequeued++;
        numAlloc++;
        ASSERT_EQ(true, frameQueue.queueItem(bufs[i], fences[i]));
    }

    while (numAlloc < firstPassAlloc) {
        c2_status_t ret = mTracker->allocate(
                0, 0, 0, kTestUsageFlag, &bufs[numAlloc], &fences[numAlloc]);
        if (ret == C2_BLOCKING) {
            mBqStat->mBlocked++;
            c2_status_t waitRes = waitFence.wait(3000000000);
            if (waitRes == C2_TIMED_OUT || waitRes == C2_OK) {
                continue;
            }
            ALOGE("alloc wait failed: c2_err(%d)", waitRes);
            break;
        }
        if (ret != C2_OK) {
            ALOGE("alloc error: c2_err(%d)", ret);
            break;
        }
        mBqStat->mDequeued++;
        numAlloc++;
    }
    ASSERT_EQ(numAlloc, firstPassAlloc);

    // switching surface
    sp<IGraphicBufferProducer> oldProducer = mProducer;
    sp<IGraphicBufferConsumer> oldConsumer = mConsumer;
    mProducer.clear();
    mConsumer.clear();
    BufferQueue::createBufferQueue(&mProducer, &mConsumer);
    ASSERT_TRUE((bool)mProducer && (bool)mConsumer);

    generation += 1;

    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));
    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));

    ASSERT_EQ(OK, oldProducer->disconnect(NATIVE_WINDOW_API_MEDIA));
    oldProducer.clear();
    oldConsumer.clear();

    for (int i = firstPassRender ; i < firstPassAlloc; ++i) {
        ASSERT_EQ(true, frameQueue.queueItem(bufs[i], fences[i]));
    }

    while (numAlloc < maxNumAlloc) {
        AHardwareBuffer *buf;
        sp<Fence> fence;
        c2_status_t ret = mTracker->allocate(0, 0, 0, kTestUsageFlag, &buf, &fence);
        if (ret == C2_BLOCKING) {
            mBqStat->mBlocked++;
            c2_status_t waitRes = waitFence.wait(3000000000);
            if (waitRes == C2_TIMED_OUT || waitRes == C2_OK) {
                continue;
            }
            ALOGE("alloc wait failed: c2_err(%d)", waitRes);
            break;
        }
        if (ret != C2_OK) {
            ALOGE("alloc error: c2_err(%d)", ret);
            break;
        }
        mBqStat->mDequeued++;
        if (!frameQueue.queueItem(buf, fence)) {
            ALOGE("queue to render failed");
            break;
        }
        ++numAlloc;
    }

    ASSERT_EQ(numAlloc, maxNumAlloc);

    frameQueue.stop(true);
    // Wait more than enough time(1 sec) to render all queued frames for sure.
    ::usleep(1000000);

    if (queueThread.joinable()) {
        queueThread.join();
    }
    // mReleased should not be checked. IProducerListener::onBufferReleased()
    // from the previous Surface could be missing after a new Surface was
    // configured. Instead check # of dequeueable and queueBuffer() calls.
    ASSERT_EQ(numAlloc, mBqStat->mQueued);
    ASSERT_EQ(maxDequeueCount, mTracker->getCurDequeueable());

    for (int i = 0; i < maxDequeueCount; ++i) {
        AHardwareBuffer *buf;
        sp<Fence> fence;

        ASSERT_EQ(C2_OK, mTracker->allocate(
                0, 0, 0, kTestUsageFlag, &buf, &fence));
        AHardwareBuffer_release(buf);
        mBqStat->mDequeued++;
        numAlloc++;
    }
    ASSERT_EQ(C2_BLOCKING, mTracker->allocate(
            0, 0, 0, kTestUsageFlag, &bufs[0], &fences[0]));
}

TEST_F(GraphicsTrackerTest, maxDequeueIncreaseTest) {
    uint32_t generation = 1;
    int maxDequeueCount = 10;
    int dequeueIncrease = 4;

    int numAlloc = 0;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));

    int waitFd = -1;
    ASSERT_EQ(C2_OK, mTracker->getWaitableFd(&waitFd));
    C2Fence waitFence = _C2FenceFactory::CreatePipeFence(waitFd);

    AHardwareBuffer *buf;
    sp<Fence> fence;
    uint64_t bids[maxDequeueCount];
    if (__builtin_available(android __ANDROID_API_T__, *)) {
        for (int i = 0; i < maxDequeueCount; ++i) {
            ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
            ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
            ASSERT_EQ(OK, AHardwareBuffer_getId(buf, &bids[i]));
            AHardwareBuffer_release(buf);
            mBqStat->mDequeued++;
            numAlloc++;
        }
    } else {
        GTEST_SKIP();
    }
    ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
    ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));

    ASSERT_EQ(C2_OK, mTracker->deallocate(bids[0], Fence::NO_FENCE));
    mBqStat->mDiscarded++;

    maxDequeueCount += dequeueIncrease;
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));
    for (int i = 0; i < dequeueIncrease + 1; ++i) {
        ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
        ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
        AHardwareBuffer_release(buf);
        mBqStat->mDequeued++;
        numAlloc++;
    }
    ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
    ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));

    ASSERT_EQ(C2_OK, mTracker->deallocate(bids[1], Fence::NO_FENCE));
    mBqStat->mDiscarded++;

    maxDequeueCount += dequeueIncrease;
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));
    for (int i = 0; i < dequeueIncrease + 1; ++i) {
        ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
        ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
        AHardwareBuffer_release(buf);
        mBqStat->mDequeued++;
        numAlloc++;
    }
    ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
    ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
}

TEST_F(GraphicsTrackerTest, maxDequeueDecreaseTest) {
    uint32_t generation = 1;
    int maxDequeueCount = 12;
    int dequeueDecrease = 4;

    int numAlloc = 0;

    ASSERT_TRUE(init(maxDequeueCount));
    ASSERT_TRUE(configure(new TestProducerListener(mTracker, mBqStat, generation),
                          new TestConsumerListener(mConsumer), 1, false));

    ASSERT_EQ(OK, mProducer->setGenerationNumber(generation));
    ASSERT_EQ(C2_OK, mTracker->configureGraphics(mProducer, generation));

    int waitFd = -1;
    ASSERT_EQ(C2_OK, mTracker->getWaitableFd(&waitFd));
    C2Fence waitFence = _C2FenceFactory::CreatePipeFence(waitFd);

    AHardwareBuffer *buf;
    sp<Fence> fence;
    uint64_t bids[maxDequeueCount];
    if (__builtin_available(android __ANDROID_API_T__, *)) {
        for (int i = 0; i < maxDequeueCount; ++i) {
            ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
            ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
            ASSERT_EQ(OK, AHardwareBuffer_getId(buf, &bids[i]));
            AHardwareBuffer_release(buf);
            mBqStat->mDequeued++;
            numAlloc++;
        }
    } else {
        GTEST_SKIP();
    }
    ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
    ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));

    int discardIdx = 0;
    maxDequeueCount -= dequeueDecrease;
    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));
    for (int i = 0; i < dequeueDecrease + 1; ++i) {
        ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
        ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
        ASSERT_EQ(C2_OK, mTracker->deallocate(bids[discardIdx++], Fence::NO_FENCE));
        mBqStat->mDiscarded++;
    }
    ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
    ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
    mBqStat->mDequeued++;

    ASSERT_EQ(C2_OK, mTracker->deallocate(bids[discardIdx++], Fence::NO_FENCE));
    mBqStat->mDiscarded++;
    ASSERT_EQ(C2_OK, mTracker->deallocate(bids[discardIdx++], Fence::NO_FENCE));
    mBqStat->mDiscarded++;
    maxDequeueCount -= dequeueDecrease;

    ASSERT_EQ(C2_OK, mTracker->configureMaxDequeueCount(maxDequeueCount));
    for (int i = 0; i < dequeueDecrease - 1; ++i) {
        ASSERT_EQ(C2_TIMED_OUT, waitFence.wait(1000000000));
        ASSERT_EQ(C2_BLOCKING, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
        ASSERT_EQ(C2_OK, mTracker->deallocate(bids[discardIdx++], Fence::NO_FENCE));
        mBqStat->mDiscarded++;
    }
    ASSERT_EQ(C2_OK, waitFence.wait(1000000000));
    ASSERT_EQ(C2_OK, mTracker->allocate( 0, 0, 0, kTestUsageFlag, &buf, &fence));
    mBqStat->mDequeued++;
}
