/*
 * Copyright (C) 2019 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.
 */

// Unit Test for ECOSession.

//#define LOG_NDEBUG 0
#define LOG_TAG "ECOSessionTest"

#include <android-base/unique_fd.h>
#include <android/binder_auto_utils.h>
#include <android/binder_parcel.h>
#include <cutils/ashmem.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <utils/Log.h>
#include <utils/Timers.h>

#include "FakeECOServiceInfoListener.h"
#include "FakeECOServiceStatsProvider.h"
#include "eco/ECOSession.h"
#include "eco/ECOUtils.h"

namespace android {
namespace media {
namespace eco {

using ::ndk::ScopedAStatus;

static constexpr uint32_t kTestWidth = 1280;
static constexpr uint32_t kTestHeight = 720;
static constexpr bool kIsCameraRecording = true;
static constexpr int32_t kTargetBitrateBps = 22000000;
static constexpr int32_t kKeyFrameIntervalFrames = 30;
static constexpr float kFrameRate = 30.0f;

// A helpful class to help create ECOSession and manage ECOSession.
class EcoSessionTest : public ::testing::Test {
public:
    EcoSessionTest() { ALOGD("EcoSessionTest created"); }

    std::shared_ptr<ECOSession> createSession(int32_t width, int32_t height,
                                              bool isCameraRecording) {
        mSession = ECOSession::createECOSession(width, height, isCameraRecording);
        if (mSession == nullptr) return nullptr;
        return mSession;
    }

private:
    std::shared_ptr<ECOSession> mSession = nullptr;
};

TEST_F(EcoSessionTest, TestConstructorWithInvalidParameters) {
    // Expects failure as ECOService1.0 will only support up to 720P and camera recording case.
    EXPECT_TRUE(createSession(1920 /* width */, 1080 /* height */, true /* isCameraRecording */) ==
                nullptr);

    // Expects failure as ECOService1.0 will only support up to 720P and camera recording case.
    EXPECT_TRUE(createSession(1920 /* width */, 1080 /* height */, false /* isCameraRecording */) ==
                nullptr);

    EXPECT_TRUE(createSession(1920 /* width */, -1 /* height */, true /* isCameraRecording */) ==
                nullptr);

    EXPECT_TRUE(createSession(-1 /* width */, 1080 /* height */, true /* isCameraRecording */) ==
                nullptr);
}

TEST_F(EcoSessionTest, TestConstructorWithValidParameters) {
    // Expects success with <= 720P and is for camera recording.
    EXPECT_TRUE(createSession(1280 /* width */, 720 /* height */, true /* isCameraRecording */) !=
                nullptr);

    // Expects success with <= 720P and is for camera recording.
    EXPECT_TRUE(createSession(640 /* width */, 480 /* height */, true /* isCameraRecording */) !=
                nullptr);
}

TEST_F(EcoSessionTest, TestAddProviderWithoutSpecifyEcoDataType) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig;
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestAddProviderWithWrongEcoDataType) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestAddNormalProvider) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res);
    EXPECT_TRUE(status.isOk());
}

// Add two providers and expect failure as ECOService1.0 only supports one provider and one
// listener.
TEST_F(EcoSessionTest, TestAddTwoProvider) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider1 =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res);
    EXPECT_TRUE(status.isOk());

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider2 =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);
    status = ecoSession->addStatsProvider(fakeProvider2, providerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestAddListenerWithDifferentHeight) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth - 1, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestAddListenerWithDifferentWidth) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight - 1,
                                                                 kIsCameraRecording, ecoSession);

    ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestAddListenerWithCameraRecordingFalse) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 !kIsCameraRecording, ecoSession);

    ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res);
    EXPECT_FALSE(status.isOk());
}

// Test the ECOSession with FakeECOServiceStatsProvider and FakeECOServiceInfoListener. Push the
// stats to ECOSession through FakeECOServiceStatsProvider and check the info received in
// from FakeECOServiceInfoListener ECOSession.
TEST_F(EcoSessionTest, TestSessionWithProviderAndListenerSimpleTest) {
    // The time that listener needs to wait for the info from ECOService.
    static constexpr int kServiceWaitTimeMs = 10;

    // Create the session.
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);

    // Add provider.
    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);
    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    providerConfig.setString(KEY_PROVIDER_NAME, "FakeECOServiceStatsProvider");
    providerConfig.setInt32(KEY_PROVIDER_TYPE,
                            ECOServiceStatsProvider::STATS_PROVIDER_TYPE_VIDEO_ENCODER);
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res);

    // Create listener.
    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    // Create the listener config.
    ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener");
    listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA);

    // Specify the qp thresholds for receiving notification.
    listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40);
    listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5);

    status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res);

    ECOData info;
    bool getInfo = false;

    // Set the getInfo flag to true and copy the info from fakeListener.
    fakeListener->setInfoAvailableCallback(
            [&info, &getInfo](const ::android::media::eco::ECOData& newInfo) {
                getInfo = true;
                info = newInfo;
            });

    // Inject the session stats into the ECOSession through fakeProvider.
    SimpleEncoderConfig sessionEncoderConfig("google-avc", CodecTypeAVC, AVCProfileHigh, AVCLevel52,
                                             kTargetBitrateBps, kKeyFrameIntervalFrames,
                                             kFrameRate);
    fakeProvider->injectSessionStats(sessionEncoderConfig.toEcoData(ECOData::DATA_TYPE_STATS));

    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));
    // Check the Session info matches with the session stats sent by provider.
    EXPECT_TRUE(getInfo);
    EXPECT_TRUE(info.getDataType() == ECOData::DATA_TYPE_INFO);

    std::string infoType;
    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_SESSION);

    // Check the session info matches the session stats provided by FakeECOServiceStatsProvider.
    int32_t codecType;
    EXPECT_TRUE(info.findInt32(ENCODER_TYPE, &codecType) == ECODataStatus::OK);
    EXPECT_EQ(codecType, CodecTypeAVC);

    int32_t profile;
    EXPECT_TRUE(info.findInt32(ENCODER_PROFILE, &profile) == ECODataStatus::OK);
    EXPECT_EQ(profile, AVCProfileHigh);

    int32_t level;
    EXPECT_TRUE(info.findInt32(ENCODER_LEVEL, &level) == ECODataStatus::OK);
    EXPECT_EQ(level, AVCLevel52);

    int32_t bitrate;
    EXPECT_TRUE(info.findInt32(ENCODER_TARGET_BITRATE_BPS, &bitrate) == ECODataStatus::OK);
    EXPECT_EQ(bitrate, kTargetBitrateBps);

    int32_t kfi;
    EXPECT_TRUE(info.findInt32(ENCODER_KFI_FRAMES, &kfi) == ECODataStatus::OK);
    EXPECT_EQ(kfi, kKeyFrameIntervalFrames);

    // =======================================================================================
    // Inject the frame stats with qp = 30. Expect receiving notification for the first frame.
    SimpleEncodedFrameData frameStats(1 /* seq number */, FrameTypeI, 0 /* framePtsUs */,
                                      30 /* avg-qp */, 56 /* frameSize */);

    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));
    // Check the Session info matches with the session stats sent by provider.
    EXPECT_TRUE(getInfo);

    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME);

    int8_t frameType;
    EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK);
    EXPECT_EQ(frameType, FrameTypeI);

    int32_t frameNum;
    EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK);
    EXPECT_EQ(frameNum, 1);

    int64_t framePtsUs;
    EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK);
    EXPECT_EQ(framePtsUs, 0);

    int32_t frameQp;
    EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK);
    EXPECT_EQ(frameQp, 30);

    int32_t frameSize;
    EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK);
    EXPECT_EQ(frameSize, 56);

    // =======================================================================================
    // Inject the frame stats with qp = 35. Expect not receiving notification as 35 is below
    // threshold.
    frameStats = SimpleEncodedFrameData(2 /* seq number */, FrameTypeP, 333333 /* framePtsUs */,
                                        35 /* avg-qp */, 56 /* frameSize */);
    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));
    EXPECT_FALSE(getInfo);

    // =======================================================================================
    // Inject the frame stats with qp = 41. Expect receiving notification as 41 goes beyond
    // threshold 40.
    frameStats = SimpleEncodedFrameData(3 /* seq number */, FrameTypeP, 666666 /* framePtsUs */,
                                        41 /* avg-qp */, 56 /* frameSize */);
    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // Check the frame info matches with the frame stats sent by provider.
    EXPECT_TRUE(getInfo);
    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME);
    EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK);
    EXPECT_EQ(frameType, FrameTypeP);
    EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK);
    EXPECT_EQ(frameNum, 3);
    EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK);
    EXPECT_EQ(framePtsUs, 666666);
    EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK);
    EXPECT_EQ(frameQp, 41);
    EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK);
    EXPECT_EQ(frameSize, 56);

    // =======================================================================================
    // Inject the frame stats with qp = 42. Expect not receiving notification as 42 goes beyond
    // threshold 40 but delta oes not go beyond the mQpChangeThreshold threshold.
    frameStats = SimpleEncodedFrameData(4 /* seq number */, FrameTypeP, 999999 /* framePtsUs */,
                                        42 /* avg-qp */, 56 /* frameSize */);
    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));
    EXPECT_FALSE(getInfo);

    // =======================================================================================
    // Inject the frame stats with qp = 10. Expect receiving notification as the detal from
    // last reported QP is larger than threshold 4.
    frameStats = SimpleEncodedFrameData(5 /* seq number */, FrameTypeB, 1333332 /* framePtsUs */,
                                        49 /* avg-qp */, 56 /* frameSize */);
    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // Check the frame info matches with the frame stats sent by provider.
    EXPECT_TRUE(getInfo);
    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME);
    EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK);
    EXPECT_EQ(frameType, FrameTypeB);
    EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK);
    EXPECT_EQ(frameNum, 5);
    EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK);
    EXPECT_EQ(framePtsUs, 1333332);
    EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK);
    EXPECT_EQ(frameQp, 49);
    EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK);
    EXPECT_EQ(frameSize, 56);

    // =======================================================================================
    // Inject the frame stats with qp = 41. Expect receiving notification as the detal from
    // last reported QP is larger than threshold 4.
    frameStats = SimpleEncodedFrameData(6 /* seq number */, FrameTypeB, 1666665 /* framePtsUs */,
                                        41 /* avg-qp */, 56 /* frameSize */);
    getInfo = false;
    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // Check the frame info matches with the frame stats sent by provider.
    EXPECT_TRUE(getInfo);
    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME);
    EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK);
    EXPECT_EQ(frameType, FrameTypeB);
    EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK);
    EXPECT_EQ(frameNum, 6);
    EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK);
    EXPECT_EQ(framePtsUs, 1666665);
    EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK);
    EXPECT_EQ(frameQp, 41);
    EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK);
    EXPECT_EQ(frameSize, 56);
}

TEST_F(EcoSessionTest, TestRemoveMatchProvider) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider1 =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res);
    EXPECT_TRUE(res);
    EXPECT_TRUE(status.isOk());

    status = ecoSession->removeStatsProvider(fakeProvider1, &res);
    EXPECT_TRUE(res);
    EXPECT_TRUE(status.isOk());
}

TEST_F(EcoSessionTest, TestRemoveMisMatchProvider) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider1 =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res);
    EXPECT_TRUE(res);
    EXPECT_TRUE(status.isOk());

    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider2 =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);

    status = ecoSession->removeStatsProvider(fakeProvider2, &res);
    EXPECT_FALSE(res);
    EXPECT_FALSE(status.isOk());
}

TEST_F(EcoSessionTest, TestRemoveMatchListener) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    // Create listener.
    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    // Create the listener config.
    ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener");
    listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA);

    // Specify the qp thresholds for receiving notification.
    listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40);
    listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5);

    bool res;
    ScopedAStatus status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res);

    status = ecoSession->removeInfoListener(fakeListener, &res);
    EXPECT_TRUE(res);
    EXPECT_TRUE(status.isOk());
}

TEST_F(EcoSessionTest, TestRemoveMisMatchListener) {
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);
    EXPECT_TRUE(ecoSession);

    // Create listener.
    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    // Create the listener config.
    ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener");
    listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA);

    // Specify the qp thresholds for receiving notification.
    listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40);
    listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5);

    bool res;
    ScopedAStatus status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res);

    // Create listener.
    std::shared_ptr<FakeECOServiceInfoListener> fakeListener2 =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    status = ecoSession->removeInfoListener(fakeListener2, &res);
    EXPECT_FALSE(res);
    EXPECT_FALSE(status.isOk());
}

// Test the listener connects to the ECOSession after provider sends the session info. Listener
// should recieve the session info right after adding itself to the ECOSession.
TEST_F(EcoSessionTest, TestAddListenerAferProviderStarts) {
    // The time that listener needs to wait for the info from ECOService.
    static constexpr int kServiceWaitTimeMs = 10;

    // Create the session.
    std::shared_ptr<ECOSession> ecoSession =
            createSession(kTestWidth, kTestHeight, kIsCameraRecording);

    // Add provider.
    std::shared_ptr<FakeECOServiceStatsProvider> fakeProvider =
            ndk::SharedRefBase::make<FakeECOServiceStatsProvider>(
                    kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession);
    ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    providerConfig.setString(KEY_PROVIDER_NAME, "FakeECOServiceStatsProvider");
    providerConfig.setInt32(KEY_PROVIDER_TYPE,
                            ECOServiceStatsProvider::STATS_PROVIDER_TYPE_VIDEO_ENCODER);
    bool res;
    ScopedAStatus status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res);

    // Inject the session stats into the ECOSession through fakeProvider.
    SimpleEncoderConfig sessionEncoderConfig("google-avc", CodecTypeAVC, AVCProfileHigh, AVCLevel52,
                                             kTargetBitrateBps, kKeyFrameIntervalFrames,
                                             kFrameRate);
    fakeProvider->injectSessionStats(sessionEncoderConfig.toEcoData(ECOData::DATA_TYPE_STATS));

    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // =======================================================================================
    // Inject the frame stats with qp = 30. Expect receiving notification for the first frame.
    SimpleEncodedFrameData frameStats(1 /* seq number */, FrameTypeI, 0 /* framePtsUs */,
                                      30 /* avg-qp */, 56 /* frameSize */);

    fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS));
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // =======================================================================================
    // Create and add the listener to the ECOSession. Expect to receive the session infor right
    // after addInfoListener.
    std::shared_ptr<FakeECOServiceInfoListener> fakeListener =
            ndk::SharedRefBase::make<FakeECOServiceInfoListener>(kTestWidth, kTestHeight,
                                                                 kIsCameraRecording, ecoSession);

    // Create the listener config.
    ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG,
                           systemTime(SYSTEM_TIME_BOOTTIME));
    listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener");
    listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA);

    // Specify the qp thresholds for receiving notification.
    listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40);
    listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5);

    ECOData info;
    bool getInfo = false;

    // Set the getInfo flag to true and copy the info from fakeListener.
    fakeListener->setInfoAvailableCallback(
            [&info, &getInfo](const ::android::media::eco::ECOData& newInfo) {
                getInfo = true;
                info = newInfo;
            });

    status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res);

    // Wait as ECOService may take some time to process.
    std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs));

    // Check the Session info matches with the session stats sent by provider.
    EXPECT_TRUE(getInfo);
    EXPECT_TRUE(info.getDataType() == ECOData::DATA_TYPE_INFO);

    std::string infoType;
    EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK);
    EXPECT_EQ(infoType, VALUE_INFO_TYPE_SESSION);

    // Check the session info matches the session stats provided by FakeECOServiceStatsProvider.
    int32_t codecType;
    EXPECT_TRUE(info.findInt32(ENCODER_TYPE, &codecType) == ECODataStatus::OK);
    EXPECT_EQ(codecType, CodecTypeAVC);

    int32_t profile;
    EXPECT_TRUE(info.findInt32(ENCODER_PROFILE, &profile) == ECODataStatus::OK);
    EXPECT_EQ(profile, AVCProfileHigh);

    int32_t level;
    EXPECT_TRUE(info.findInt32(ENCODER_LEVEL, &level) == ECODataStatus::OK);
    EXPECT_EQ(level, AVCLevel52);

    int32_t bitrate;
    EXPECT_TRUE(info.findInt32(ENCODER_TARGET_BITRATE_BPS, &bitrate) == ECODataStatus::OK);
    EXPECT_EQ(bitrate, kTargetBitrateBps);

    int32_t kfi;
    EXPECT_TRUE(info.findInt32(ENCODER_KFI_FRAMES, &kfi) == ECODataStatus::OK);
    EXPECT_EQ(kfi, kKeyFrameIntervalFrames);
}

}  // namespace eco
}  // namespace media
}  // namespace android
