/*
 * 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "NativeExtractorUnitTest"
#include <log/log.h>

#include <jni.h>
#include <media/NdkMediaExtractor.h>
#include <sys/stat.h>

#include <cstdlib>
#include <cstring>

static media_status_t setExtractorDataSource(AMediaExtractor* extractor, FILE* fp) {
    media_status_t status = AMEDIA_ERROR_BASE;
    struct stat buf {};
    if (fp && !fstat(fileno(fp), &buf))
        status = AMediaExtractor_setDataSourceFd(extractor, fileno(fp), 0, buf.st_size);
    if (status != AMEDIA_OK) ALOGE("error: AMediaExtractor_setDataSourceFd failed %d", status);
    return status;
}

static jboolean nativeTestGetTrackCountBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = (0 == AMediaExtractor_getTrackCount(extractor));
    if (!isPass) ALOGE("error: received valid trackCount before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestSelectTrackBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = (AMEDIA_OK != AMediaExtractor_selectTrack(extractor, 0));
    if (!isPass) ALOGE("error: selectTrack succeeds before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestSelectTrackForInvalidIndex(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        if (AMEDIA_OK !=
            AMediaExtractor_selectTrack(extractor, AMediaExtractor_getTrackCount(extractor))) {
            isPass = true;
        } else {
            ALOGE("error: selectTrack succeeds for out of bounds track index: %zu",
                  AMediaExtractor_getTrackCount(extractor));
        }
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIdempotentSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_selectTrack(extractor, 0) == AMEDIA_OK;
        isPass &= AMediaExtractor_selectTrack(extractor, 0) == AMEDIA_OK;
        if (!isPass) ALOGE("error: multiple selection of same track has failed");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestUnselectTrackBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = (AMEDIA_OK != AMediaExtractor_unselectTrack(extractor, 0));
    if (!isPass) ALOGE("error: unselectTrack succeeds before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestUnselectTrackForInvalidIndex(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        if (AMEDIA_OK !=
            AMediaExtractor_unselectTrack(extractor, AMediaExtractor_getTrackCount(extractor))) {
            isPass = true;
        } else {
            ALOGE("error: unselectTrack succeeds for out of bounds track index: %zu",
                  AMediaExtractor_getTrackCount(extractor));
        }
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestUnselectTrackForUnSelectedTrackIndex(JNIEnv* env, jobject,
                                                               jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = (AMediaExtractor_unselectTrack(extractor, 0) == AMEDIA_OK);
        if (!isPass) ALOGE("error: un-selection of non-selected track has failed");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIdempotentUnselectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_selectTrack(extractor, 0) == AMEDIA_OK;
        if (isPass) {
            isPass = AMediaExtractor_unselectTrack(extractor, 0) == AMEDIA_OK;
            isPass &= AMediaExtractor_unselectTrack(extractor, 0) == AMEDIA_OK;
            if (!isPass) ALOGE("error: multiple unselection of selected track has failed");
        } else {
            ALOGE("error: selection of track 0 has failed for file %s", csrcPath);
        }
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestSeekToBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC) != AMEDIA_OK;
    if (!isPass) ALOGE("error: seekTo() succeeds before setting data source");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestSeekToBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC) != AMEDIA_OK;
        if (!isPass) ALOGE("error: seekTo() succeeds before selecting track");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetCachedDurationBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_getCachedDuration(extractor) == -1;
    if (!isPass) ALOGE("error: getCachedDuration returns unexpected value before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIfGetFileFormatSucceedsBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* empty = AMediaFormat_new();
    AMediaFormat* format = AMediaExtractor_getFileFormat(extractor);
    bool isPass = true;
    if (format == nullptr ||
        strcmp(AMediaFormat_toString(empty), AMediaFormat_toString(format)) != 0) {
        isPass = false;
        ALOGE("error: getFileFormat before set data source yields unexpected result");
    }
    if (format) AMediaFormat_delete(format);
    AMediaFormat_delete(empty);
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestAdvanceBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = !AMediaExtractor_advance(extractor);
    if (!isPass) ALOGE("error: advance succeeds before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestAdvanceBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = !AMediaExtractor_advance(extractor);
        if (!isPass) ALOGE("error: advance succeeds without any active tracks");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleFlagsBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_getSampleFlags(extractor) == -1;
    if (!isPass) ALOGE("error: received valid sample flag before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleFlagsBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_getSampleFlags(extractor) == -1;
        if (!isPass) ALOGE("error: received valid sample flag without any active tracks");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleTimeBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_getSampleTime(extractor) == -1;
    if (!isPass) ALOGE("error: received valid pts before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleTimeBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    AMediaExtractor* extractor = AMediaExtractor_new();
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_getSampleTime(extractor) == -1;
        if (!isPass) ALOGE("error: received valid pts without any active tracks");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleSizeBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_getSampleSize(extractor) == -1;
    if (!isPass) ALOGE("error: received valid sample size before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleSizeBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_getSampleSize(extractor) == -1;
        if (!isPass) ALOGE("error: received valid sample size without any active tracks");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIfGetSampleFormatBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* format = AMediaFormat_new();
    bool isPass = AMediaExtractor_getSampleFormat(extractor, format) != AMEDIA_OK;
    if (!isPass) ALOGE("error: getSampleFormat succeeds before setDataSource");
    AMediaFormat_delete(format);
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIfGetSampleFormatBeforeSelectTrack(JNIEnv* env, jobject,
                                                             jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* format = AMediaFormat_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_getSampleFormat(extractor, format) != AMEDIA_OK;
        if (!isPass) ALOGE("error: getSampleFormat succeeds without any active tracks");
    }
    AMediaFormat_delete(format);
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestVideoSampleFileOffsetByGetSampleFormat(JNIEnv* env, jobject,
                                                             jstring jsrcPath) {
    int64_t video_sample_offsets[] = {6522, 95521, 118719, 126219, 137578};
    bool isPass = true;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* format = AMediaFormat_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        if (AMEDIA_OK == AMediaExtractor_selectTrack(extractor, 0 /* video */)) {
            for(int i = 0; i < sizeof(video_sample_offsets)/sizeof(int64_t); ++i) {
                if (AMEDIA_OK == AMediaExtractor_getSampleFormat(extractor, format)) {
                    ALOGV("AMediaFormat_toString:%s", AMediaFormat_toString(format));
                    int64_t offset = 0;
                    if (AMediaFormat_getInt64(format, "sample-file-offset", &offset)) {
                        if (offset != video_sample_offsets[i]) {
                            ALOGD("offset:%lld, video_sample_offsets[%d]:%lld",
                                        (long long)offset, i, (long long)video_sample_offsets[i]);
                            isPass = false;
                            break;
                        }
                    } else {
                        ALOGD("error: sample-file-offset not found");
                        isPass = false;
                        break;
                    }
                }
                AMediaExtractor_advance(extractor);
            }
        }
    }
    AMediaExtractor_delete(extractor);
    AMediaFormat_delete(format);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestAudioSampleFileOffsetByGetSampleFormat(JNIEnv* env, jobject,
                                                             jstring jsrcPath) {
    int64_t audio_sample_offsets[] = {186125, 186682, 187286, 187944, 188551};
    bool isPass = true;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* format = AMediaFormat_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        if (AMEDIA_OK == AMediaExtractor_selectTrack(extractor, 1 /* audio */)) {
            for(int i = 0; i < sizeof(audio_sample_offsets)/sizeof(int64_t); ++i) {
                if (AMEDIA_OK == AMediaExtractor_getSampleFormat(extractor, format)) {
                    ALOGV("AMediaFormat_toString:%s", AMediaFormat_toString(format));
                    int64_t offset = 0;
                    if (AMediaFormat_getInt64(format, "sample-file-offset", &offset)) {
                        if (offset != audio_sample_offsets[i]) {
                            ALOGD("offset:%lld, audio_sample_offsets[%d]:%lld",
                                        (long long)offset, i, (long long)audio_sample_offsets[i]);
                            isPass = false;
                            break;
                        }
                    } else {
                        ALOGE("error: sample-file-offset not found");
                        isPass = false;
                        break;
                    }
                }
                AMediaExtractor_advance(extractor);
            }
        }
    }
    AMediaExtractor_delete(extractor);
    AMediaFormat_delete(format);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleTrackIndexBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_getSampleTrackIndex(extractor) == -1;
    if (!isPass) ALOGE("error: received valid track index before setDataSource");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetSampleTrackIndexBeforeSelectTrack(JNIEnv* env, jobject,
                                                               jstring jsrcPath) {
    bool isPass = false;
    AMediaExtractor* extractor = AMediaExtractor_new();
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_getSampleTrackIndex(extractor) == -1;
        if (!isPass) ALOGE("error: received valid track index without any active tracks");
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetTrackFormatBeforeSetDataSource(JNIEnv*, jobject) {
    bool isPass = true;
    AMediaExtractor* extractor = AMediaExtractor_new();
    AMediaFormat* empty = AMediaFormat_new();
    AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, 0);
    if (format == nullptr ||
        strcmp(AMediaFormat_toString(empty), AMediaFormat_toString(format)) != 0) {
        isPass = false;
        ALOGE("error: getTrackFormat before setDataSource yields unexpected result");
    }
    if (format) AMediaFormat_delete(format);
    AMediaFormat_delete(empty);
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestGetTrackFormatForInvalidIndex(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = true;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        AMediaFormat* format =
                AMediaExtractor_getTrackFormat(extractor, AMediaExtractor_getTrackCount(extractor));
        AMediaFormat* empty = AMediaFormat_new();
        if (format == nullptr ||
            strcmp(AMediaFormat_toString(empty), AMediaFormat_toString(format)) != 0) {
            isPass = false;
            ALOGE("error: getTrackFormat for out of bound track index %zu yields unexpected result",
                  AMediaExtractor_getTrackCount(extractor));
        }
        if (format) AMediaFormat_delete(format);
        AMediaFormat_delete(empty);
    } else {
        isPass = false;
    }
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestReadSampleDataBeforeSetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    const int maxSampleSize = 512 * 1024;
    auto buffer = new uint8_t[maxSampleSize];
    bool isPass = AMediaExtractor_readSampleData(extractor, buffer, maxSampleSize) < 0;
    if (!isPass) ALOGE("error: readSampleData return non-negative readBytes before setDataSource");
    delete[] buffer;
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestReadSampleDataBeforeSelectTrack(JNIEnv* env, jobject, jstring jsrcPath) {
    bool isPass = false;
    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
    AMediaExtractor* extractor = AMediaExtractor_new();
    const int maxSampleSize = (512 * 1024);
    auto buffer = new uint8_t[maxSampleSize];
    FILE* srcFp = fopen(csrcPath, "rbe");
    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
        isPass = AMediaExtractor_readSampleData(extractor, buffer, maxSampleSize) < 0;
        if (!isPass) {
            ALOGE("error: readSampleData returns non-negative readBytes with out active tracks");
        }
    }
    delete[] buffer;
    AMediaExtractor_delete(extractor);
    if (srcFp) fclose(srcFp);
    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
    return static_cast<jboolean>(isPass);
}

static jboolean nativeTestIfNullLocationIsRejectedBySetDataSource(JNIEnv*, jobject) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    bool isPass = AMediaExtractor_setDataSource(extractor, nullptr) != AMEDIA_OK;
    if (!isPass) ALOGE("error: setDataSource succeeds with null location");
    AMediaExtractor_delete(extractor);
    return static_cast<jboolean>(isPass);
}

int registerAndroidMediaV2CtsExtractorUnitTestApi(JNIEnv* env) {
    const JNINativeMethod methodTable[] = {
            {"nativeTestGetTrackCountBeforeSetDataSource", "()Z",
             (void*)nativeTestGetTrackCountBeforeSetDataSource},
            {"nativeTestSelectTrackBeforeSetDataSource", "()Z",
             (void*)nativeTestSelectTrackBeforeSetDataSource},
            {"nativeTestSelectTrackForInvalidIndex", "(Ljava/lang/String;)Z",
             (void*)nativeTestSelectTrackForInvalidIndex},
            {"nativeTestIdempotentSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestIdempotentSelectTrack},
            {"nativeTestUnselectTrackBeforeSetDataSource", "()Z",
             (void*)nativeTestUnselectTrackBeforeSetDataSource},
            {"nativeTestUnselectTrackForInvalidIndex", "(Ljava/lang/String;)Z",
             (void*)nativeTestUnselectTrackForInvalidIndex},
            {"nativeTestUnselectTrackForUnSelectedTrackIndex", "(Ljava/lang/String;)Z",
             (void*)nativeTestUnselectTrackForUnSelectedTrackIndex},
            {"nativeTestIdempotentUnselectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestIdempotentUnselectTrack},
            {"nativeTestSeekToBeforeSetDataSource", "()Z",
             (void*)nativeTestSeekToBeforeSetDataSource},
            {"nativeTestSeekToBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestSeekToBeforeSelectTrack},
            {"nativeTestGetCachedDurationBeforeSetDataSource", "()Z",
             (void*)nativeTestGetCachedDurationBeforeSetDataSource},
            {"nativeTestIfGetFileFormatSucceedsBeforeSetDataSource", "()Z",
             (void*)nativeTestIfGetFileFormatSucceedsBeforeSetDataSource},
            {"nativeTestAdvanceBeforeSetDataSource", "()Z",
             (void*)nativeTestAdvanceBeforeSetDataSource},
            {"nativeTestAdvanceBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestAdvanceBeforeSelectTrack},
            {"nativeTestGetSampleFlagsBeforeSetDataSource", "()Z",
             (void*)nativeTestGetSampleFlagsBeforeSetDataSource},
            {"nativeTestGetSampleFlagsBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestGetSampleFlagsBeforeSelectTrack},
            {"nativeTestGetSampleTimeBeforeSetDataSource", "()Z",
             (void*)nativeTestGetSampleTimeBeforeSetDataSource},
            {"nativeTestGetSampleTimeBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestGetSampleTimeBeforeSelectTrack},
            {"nativeTestGetSampleSizeBeforeSetDataSource", "()Z",
             (void*)nativeTestGetSampleSizeBeforeSetDataSource},
            {"nativeTestGetSampleSizeBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestGetSampleSizeBeforeSelectTrack},
            {"nativeTestIfGetSampleFormatBeforeSetDataSource", "()Z",
             (void*)nativeTestIfGetSampleFormatBeforeSetDataSource},
            {"nativeTestIfGetSampleFormatBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestIfGetSampleFormatBeforeSelectTrack},
            {"nativeTestGetSampleTrackIndexBeforeSetDataSource", "()Z",
             (void*)nativeTestGetSampleTrackIndexBeforeSetDataSource},
            {"nativeTestGetSampleTrackIndexBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestGetSampleTrackIndexBeforeSelectTrack},
            {"nativeTestGetTrackFormatBeforeSetDataSource", "()Z",
             (void*)nativeTestGetTrackFormatBeforeSetDataSource},
            {"nativeTestGetTrackFormatForInvalidIndex", "(Ljava/lang/String;)Z",
             (void*)nativeTestGetTrackFormatForInvalidIndex},
            {"nativeTestReadSampleDataBeforeSetDataSource", "()Z",
             (void*)nativeTestReadSampleDataBeforeSetDataSource},
            {"nativeTestReadSampleDataBeforeSelectTrack", "(Ljava/lang/String;)Z",
             (void*)nativeTestReadSampleDataBeforeSelectTrack},
            {"nativeTestIfNullLocationIsRejectedBySetDataSource", "()Z",
             (void*)nativeTestIfNullLocationIsRejectedBySetDataSource},
            {"nativeTestVideoSampleFileOffsetByGetSampleFormat", "(Ljava/lang/String;)Z",
             (void*)nativeTestVideoSampleFileOffsetByGetSampleFormat},
            {"nativeTestAudioSampleFileOffsetByGetSampleFormat", "(Ljava/lang/String;)Z",
             (void*)nativeTestAudioSampleFileOffsetByGetSampleFormat},
    };
    jclass c = env->FindClass("android/mediav2/cts/ExtractorUnitTest$TestApiNative");
    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
}
