/*
 * Copyright (C) 2008 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 "AudioRecord-JNI"

#include <android/content/AttributionSourceState.h>
#include <android_os_Parcel.h>
#include <inttypes.h>
#include <jni.h>
#include <media/AudioRecord.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include <utils/Log.h>

#include <vector>

#include "android_media_AudioAttributes.h"
#include "android_media_AudioErrors.h"
#include "android_media_AudioFormat.h"
#include "android_media_DeviceCallback.h"
#include "android_media_JNIUtils.h"
#include "android_media_MediaMetricsJNI.h"
#include "android_media_MicrophoneInfo.h"
#include "core_jni_helpers.h"

// ----------------------------------------------------------------------------

using namespace android;

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioRecord";

static jclass gArrayListClass;
static struct {
    jmethodID add;
} gArrayListMethods;

struct audio_record_fields_t {
    // these fields provide access from C++ to the...
    jmethodID postNativeEventInJava; //... event post callback method
    jfieldID  nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object
    jfieldID  jniData;    // provides access to AudioRecord JNI Handle
};
static audio_record_fields_t     javaAudioRecordFields;
static struct {
    jfieldID  fieldFramePosition;     // AudioTimestamp.framePosition
    jfieldID  fieldNanoTime;          // AudioTimestamp.nanoTime
} javaAudioTimestampFields;


class AudioRecordJNIStorage : public AudioRecord::IAudioRecordCallback {
 private:
   // Keep in sync with frameworks/base/media/java/android/media/AudioRecord.java NATIVE_EVENT_*.
   enum class EventType {
        EVENT_MORE_DATA = 0,        // Request to read available data from buffer.
                                    // If this event is delivered but the callback handler
                                    // does not want to read the available data, the handler must
                                    // explicitly ignore the event by setting frameCount to zero.
        EVENT_OVERRUN = 1,          // Buffer overrun occurred.
        EVENT_MARKER = 2,           // Record head is at the specified marker position
                                    // (See setMarkerPosition()).
        EVENT_NEW_POS = 3,          // Record head is at a new position
                                    // (See setPositionUpdatePeriod()).
        EVENT_NEW_IAUDIORECORD = 4, // IAudioRecord was re-created, either due to re-routing and
                                    // voluntary invalidation by mediaserver, or mediaserver crash.
    };

  public:
    AudioRecordJNIStorage(jclass audioRecordClass, jobject audioRecordWeakRef)
          : mAudioRecordClass(audioRecordClass), mAudioRecordWeakRef(audioRecordWeakRef) {}
    AudioRecordJNIStorage(const AudioRecordJNIStorage &) = delete;
    AudioRecordJNIStorage& operator=(const AudioRecordJNIStorage &) = delete;

    void onMarker(uint32_t) override {
        postEvent(EventType::EVENT_MARKER);
    }

    void onNewPos(uint32_t) override {
        postEvent(EventType::EVENT_NEW_POS);
    }

    void setDeviceCallback(const sp<JNIDeviceCallback>& callback) {
        mDeviceCallback = callback;
    }

    sp<JNIDeviceCallback> getDeviceCallback() const { return mDeviceCallback; }

    jobject getAudioTrackWeakRef() const & { return mAudioRecordWeakRef.get(); }

    // If we attempt to get a jobject from a rvalue, it will soon go out of
    // scope, and the reference count can drop to zero, which is unsafe.
    jobject getAudioTrackWeakRef() const && = delete;

  private:
    void postEvent(EventType event, int arg = 0) const {
      JNIEnv *env = getJNIEnvOrDie();
      env->CallStaticVoidMethod(
          static_cast<jclass>(mAudioRecordClass.get()),
          javaAudioRecordFields.postNativeEventInJava,
          mAudioRecordWeakRef.get(), static_cast<int>(event), arg, 0, nullptr);
      if (env->ExceptionCheck()) {
          env->ExceptionDescribe();
          env->ExceptionClear();
      }
    }

    // Mutation of this object is protected using Java concurrency constructs
    sp<JNIDeviceCallback> mDeviceCallback;
    const GlobalRef   mAudioRecordClass;
    const GlobalRef   mAudioRecordWeakRef;
};

// ----------------------------------------------------------------------------

#define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT      (-16)
#define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK  (-17)
#define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT       (-18)
#define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE       (-19)
#define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED    (-20)

// ----------------------------------------------------------------------------
static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz)
{
    return getFieldSp<AudioRecord>(env, thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                            jobject jaa, jintArray jSampleRate, jint channelMask,
                                            jint channelIndexMask, jint audioFormat,
                                            jint buffSizeInBytes, jintArray jSession,
                                            jobject jAttributionSource, jlong nativeRecordInJavaObj,
                                            jint sharedAudioHistoryMs) {
    //ALOGV(">> Entering android_media_AudioRecord_setup");
    //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
    //     "nativeRecordInJavaObj=0x%llX",
    //     sampleRateInHertz, audioFormat, channelMask, buffSizeInBytes, nativeRecordInJavaObj);
    audio_channel_mask_t localChanMask = inChannelMaskToNative(channelMask);

    if (jSession == NULL) {
        ALOGE("Error creating AudioRecord: invalid session ID pointer");
        return (jint) AUDIO_JAVA_ERROR;
    }

    jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
    if (nSession == NULL) {
        ALOGE("Error creating AudioRecord: Error retrieving session id pointer");
        return (jint) AUDIO_JAVA_ERROR;
    }
    audio_session_t sessionId = (audio_session_t) nSession[0];
    env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
    nSession = NULL;

    sp<AudioRecord> lpRecorder;
    sp<AudioRecordJNIStorage> callbackData;
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find %s when setting up callback.", kClassPathName);
        return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
    }

    // if we pass in an existing *Native* AudioRecord, we don't need to create/initialize one.
    if (nativeRecordInJavaObj == 0) {
        if (jaa == 0) {
            ALOGE("Error creating AudioRecord: invalid audio attributes");
            return (jint) AUDIO_JAVA_ERROR;
        }

        if (jSampleRate == 0) {
            ALOGE("Error creating AudioRecord: invalid sample rates");
            return (jint) AUDIO_JAVA_ERROR;
        }
        jint elements[1];
        env->GetIntArrayRegion(jSampleRate, 0, 1, elements);
        int sampleRateInHertz = elements[0];

        // channel index mask takes priority over channel position masks.
        if (channelIndexMask) {
            // Java channel index masks need the representation bits set.
            localChanMask = audio_channel_mask_from_representation_and_bits(
                    AUDIO_CHANNEL_REPRESENTATION_INDEX,
                    channelIndexMask);
        }
        // Java channel position masks map directly to the native definition

        if (!audio_is_input_channel(localChanMask)) {
            ALOGE("Error creating AudioRecord: channel mask %#x is not valid.", localChanMask);
            return (jint) AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK;
        }
        uint32_t channelCount = audio_channel_count_from_in_mask(localChanMask);

        // compare the format against the Java constants
        audio_format_t format = audioFormatToNative(audioFormat);
        if (format == AUDIO_FORMAT_INVALID) {
            ALOGE("Error creating AudioRecord: unsupported audio format %d.", audioFormat);
            return (jint) AUDIORECORD_ERROR_SETUP_INVALIDFORMAT;
        }

        size_t bytesPerSample = audio_bytes_per_sample(format);

        if (buffSizeInBytes == 0) {
            ALOGE("Error creating AudioRecord: frameCount is 0.");
            return (jint) AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT;
        }
        size_t frameSize = channelCount * bytesPerSample;
        size_t frameCount = buffSizeInBytes / frameSize;

        // create an uninitialized AudioRecord object
        Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
        android::content::AttributionSourceState attributionSource;
        attributionSource.readFromParcel(parcel);

        lpRecorder = new AudioRecord(attributionSource);

        // read the AudioAttributes values
        auto paa = JNIAudioAttributeHelper::makeUnique();
        jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
        if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
            return jStatus;
        }
        ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags);

        audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
        if (paa->flags & AUDIO_FLAG_HW_HOTWORD) {
            flags = AUDIO_INPUT_FLAG_HW_HOTWORD;
        }
        // create the callback information:
        // this data will be passed with every AudioRecord callback
        // we use a weak reference so the AudioRecord object can be garbage collected.
        callbackData = sp<AudioRecordJNIStorage>::make(clazz, weak_this);

        const status_t status =
                lpRecorder->set(paa->source, sampleRateInHertz,
                                format, // word length, PCM
                                localChanMask, frameCount,
                                callbackData,   // callback
                                0,                // notificationFrames,
                                true,             // threadCanCallJava
                                sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1,
                                -1, // default uid, pid
                                paa.get(), AUDIO_PORT_HANDLE_NONE, MIC_DIRECTION_UNSPECIFIED,
                                MIC_FIELD_DIMENSION_DEFAULT, sharedAudioHistoryMs);

        if (status != NO_ERROR) {
            ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
                    status);
            goto native_init_failure;
        }
        // Set caller name so it can be logged in destructor.
        // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_JAVA
        lpRecorder->setCallerName("java");
    } else { // end if nativeRecordInJavaObj == 0)
        lpRecorder = (AudioRecord*)nativeRecordInJavaObj;
        // TODO: We need to find out which members of the Java AudioRecord might need to be
        // initialized from the Native AudioRecord
        // these are directly returned from getters:
        //  mSampleRate
        //  mRecordSource
        //  mAudioFormat
        //  mChannelMask
        //  mChannelCount
        //  mState (?)
        //  mRecordingState (?)
        //  mPreferredDevice

        // create the callback information:
        // this data will be passed with every AudioRecord callback
        // This next line makes little sense
        // callbackData = sp<AudioRecordJNIStorage>::make(clazz, weak_this);
    }

    nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
    if (nSession == NULL) {
        ALOGE("Error creating AudioRecord: Error retrieving session id pointer");
        goto native_init_failure;
    }
    // read the audio session ID back from AudioRecord in case a new session was created during set()
    nSession[0] = lpRecorder->getSessionId();
    env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
    nSession = NULL;

    {
        const jint elements[1] = { (jint) lpRecorder->getSampleRate() };
        env->SetIntArrayRegion(jSampleRate, 0, 1, elements);
    }

    // save our newly created C++ AudioRecord in the "nativeRecorderInJavaObj" field
    // of the Java object
    setFieldSp(env, thiz, lpRecorder, javaAudioRecordFields.nativeRecorderInJavaObj);

    // save our newly created callback information in the "jniData" field
    // of the Java object (in mNativeJNIDataHandle) so we can free the memory in finalize()
    setFieldSp(env, thiz, callbackData, javaAudioRecordFields.jniData);

    return (jint) AUDIO_JAVA_SUCCESS;

    // failure:
native_init_failure:
    setFieldSp(env, thiz, sp<AudioRecord>{}, javaAudioRecordFields.nativeRecorderInJavaObj);
    setFieldSp(env, thiz, sp<AudioRecordJNIStorage>{}, javaAudioRecordFields.jniData);

    // lpRecorder goes out of scope, so reference count drops to zero
    return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
}

// ----------------------------------------------------------------------------
static jint
android_media_AudioRecord_start(JNIEnv *env, jobject thiz, jint event, jint triggerSession)
{
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return (jint) AUDIO_JAVA_ERROR;
    }

    return nativeToJavaStatus(
            lpRecorder->start((AudioSystem::sync_event_t)event, (audio_session_t) triggerSession));
}


// ----------------------------------------------------------------------------
static void
android_media_AudioRecord_stop(JNIEnv *env, jobject thiz)
{
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }

    lpRecorder->stop();
    //ALOGV("Called lpRecorder->stop()");
}


// ----------------------------------------------------------------------------

#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_AudioRecord_release(JNIEnv *env,  jobject thiz) {

    setFieldSp(env, thiz, sp<AudioRecord>{}, javaAudioRecordFields.nativeRecorderInJavaObj);
    setFieldSp(env, thiz, sp<AudioRecordJNIStorage>{}, javaAudioRecordFields.jniData);
}


// ----------------------------------------------------------------------------
static void android_media_AudioRecord_finalize(JNIEnv *env,  jobject thiz) {
    android_media_AudioRecord_release(env, thiz);
}

// overloaded JNI array helper functions
static inline
jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) {
    return env->GetByteArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) {
    env->ReleaseByteArrayElements(array, elems, mode);
}

static inline
jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) {
    return env->GetShortArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) {
    env->ReleaseShortArrayElements(array, elems, mode);
}

static inline
jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) {
    return env->GetFloatArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) {
    env->ReleaseFloatArrayElements(array, elems, mode);
}

static inline
jint interpretReadSizeError(ssize_t readSize) {
    if (readSize == WOULD_BLOCK) {
        return (jint)0;
    } else if (readSize == NO_INIT) {
        return AUDIO_JAVA_DEAD_OBJECT;
    } else {
        ALOGE("Error %zd during AudioRecord native read", readSize);
        return nativeToJavaStatus(readSize);
    }
}

template <typename T>
static jint android_media_AudioRecord_readInArray(JNIEnv *env,  jobject thiz,
                                                  T javaAudioData,
                                                  jint offsetInSamples, jint sizeInSamples,
                                                  jboolean isReadBlocking) {
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        ALOGE("Unable to retrieve AudioRecord object");
        return (jint)AUDIO_JAVA_INVALID_OPERATION;
    }

    if (javaAudioData == NULL) {
        ALOGE("Invalid Java array to store recorded audio");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such
    // a way that it becomes much more efficient. When doing so, we will have to prevent the
    // AudioSystem callback to be called while in critical section (in case of media server
    // process crash for instance)

    // get the pointer to where we'll record the audio
    auto *recordBuff = envGetArrayElements(env, javaAudioData, NULL);
    if (recordBuff == NULL) {
        ALOGE("Error retrieving destination for recorded audio data");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // read the new audio data from the native AudioRecord object
    const size_t sizeInBytes = sizeInSamples * sizeof(*recordBuff);
    ssize_t readSize = lpRecorder->read(
            recordBuff + offsetInSamples, sizeInBytes, isReadBlocking == JNI_TRUE /* blocking */);

    envReleaseArrayElements(env, javaAudioData, recordBuff, 0);

    if (readSize < 0) {
        return interpretReadSizeError(readSize);
    }
    return (jint)(readSize / sizeof(*recordBuff));
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_readInDirectBuffer(JNIEnv *env,  jobject thiz,
                                                         jobject jBuffer, jint sizeInBytes,
                                                         jboolean isReadBlocking) {
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder==NULL)
        return (jint)AUDIO_JAVA_INVALID_OPERATION;

    // direct buffer and direct access supported?
    long capacity = env->GetDirectBufferCapacity(jBuffer);
    if (capacity == -1) {
        // buffer direct access is not supported
        ALOGE("Buffer direct access is not supported, can't record");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }
    //ALOGV("capacity = %ld", capacity);
    jbyte* nativeFromJavaBuf = (jbyte*) env->GetDirectBufferAddress(jBuffer);
    if (nativeFromJavaBuf==NULL) {
        ALOGE("Buffer direct access is not supported, can't record");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // read new data from the recorder
    ssize_t readSize = lpRecorder->read(nativeFromJavaBuf,
                                        capacity < sizeInBytes ? capacity : sizeInBytes,
                                        isReadBlocking == JNI_TRUE /* blocking */);
    if (readSize < 0) {
        return interpretReadSizeError(readSize);
    }
    return (jint)readSize;
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_buffer_size_in_frames(JNIEnv *env,  jobject thiz) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for frameCount()");
        return (jint)AUDIO_JAVA_ERROR;
    }
    return lpRecorder->frameCount();
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_set_marker_pos(JNIEnv *env,  jobject thiz,
        jint markerPos) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for setMarkerPosition()");
        return (jint)AUDIO_JAVA_ERROR;
    }
    return nativeToJavaStatus( lpRecorder->setMarkerPosition(markerPos) );
}


// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_marker_pos(JNIEnv *env,  jobject thiz) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    uint32_t markerPos = 0;

    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for getMarkerPosition()");
        return (jint)AUDIO_JAVA_ERROR;
    }
    lpRecorder->getMarkerPosition(&markerPos);
    return (jint)markerPos;
}


// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_set_pos_update_period(JNIEnv *env,  jobject thiz,
        jint period) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);

    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for setPositionUpdatePeriod()");
        return (jint)AUDIO_JAVA_ERROR;
    }
    return nativeToJavaStatus( lpRecorder->setPositionUpdatePeriod(period) );
}


// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_pos_update_period(JNIEnv *env,  jobject thiz) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    uint32_t period = 0;

    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for getPositionUpdatePeriod()");
        return (jint)AUDIO_JAVA_ERROR;
    }
    lpRecorder->getPositionUpdatePeriod(&period);
    return (jint)period;
}


// ----------------------------------------------------------------------------
// returns the minimum required size for the successful creation of an AudioRecord instance.
// returns 0 if the parameter combination is not supported.
// return -1 if there was an error querying the buffer size.
static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint channelCount, jint audioFormat) {

    ALOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)",
          sampleRateInHertz, channelCount, audioFormat);

    size_t frameCount = 0;
    audio_format_t format = audioFormatToNative(audioFormat);
    status_t result = AudioRecord::getMinFrameCount(&frameCount,
            sampleRateInHertz,
            format,
            audio_channel_in_mask_from_count(channelCount));

    if (result == BAD_VALUE) {
        return 0;
    }
    if (result != NO_ERROR) {
        return -1;
    }
    return frameCount * channelCount * audio_bytes_per_sample(format);
}

static jboolean android_media_AudioRecord_setInputDevice(
        JNIEnv *env,  jobject thiz, jint device_id) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == 0) {
        return false;
    }
    return lpRecorder->setInputDevice(device_id) == NO_ERROR;
}

static jint android_media_AudioRecord_getRoutedDeviceId(
                JNIEnv *env,  jobject thiz) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == 0) {
        return 0;
    }
    return (jint)lpRecorder->getRoutedDeviceId();
}

// Enable and Disable Callback methods are synchronized on the Java side
static void android_media_AudioRecord_enableDeviceCallback(
                JNIEnv *env,  jobject thiz) {

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == nullptr) {
        return;
    }
    const auto pJniStorage =
            getFieldSp<AudioRecordJNIStorage>(env, thiz, javaAudioRecordFields.jniData);
   if (pJniStorage == nullptr || pJniStorage->getDeviceCallback() != nullptr) {
        return;
    }

    pJniStorage->setDeviceCallback(
            sp<JNIDeviceCallback>::make(env, thiz, pJniStorage->getAudioTrackWeakRef(),
                                        javaAudioRecordFields.postNativeEventInJava));
    lpRecorder->addAudioDeviceCallback(pJniStorage->getDeviceCallback());
}

static void android_media_AudioRecord_disableDeviceCallback(
                JNIEnv *env,  jobject thiz) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == nullptr) {
        return;
    }
    const auto pJniStorage =
            getFieldSp<AudioRecordJNIStorage>(env, thiz, javaAudioRecordFields.jniData);

    if (pJniStorage == nullptr || pJniStorage->getDeviceCallback() == nullptr) {
        return;
    }
    lpRecorder->removeAudioDeviceCallback(pJniStorage->getDeviceCallback());
    pJniStorage->setDeviceCallback(nullptr);
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_timestamp(JNIEnv *env, jobject thiz,
        jobject timestamp, jint timebase) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);

    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for getTimestamp()");
        return (jint)AUDIO_JAVA_ERROR;
    }

    ExtendedTimestamp ts;
    jint status = nativeToJavaStatus(lpRecorder->getTimestamp(&ts));

    if (status == AUDIO_JAVA_SUCCESS) {
        // set the data
        int64_t position, time;

        status = nativeToJavaStatus(ts.getBestTimestamp(&position, &time, timebase));
        if (status == AUDIO_JAVA_SUCCESS) {
            env->SetLongField(
                    timestamp, javaAudioTimestampFields.fieldFramePosition, position);
            env->SetLongField(
                    timestamp, javaAudioTimestampFields.fieldNanoTime, time);
        }
    }
    return status;
}

// ----------------------------------------------------------------------------
static jobject
android_media_AudioRecord_native_getMetrics(JNIEnv *env, jobject thiz)
{
    ALOGV("android_media_AudioRecord_native_getMetrics");

    sp<AudioRecord> lpRecord = getAudioRecord(env, thiz);

    if (lpRecord == NULL) {
        ALOGE("Unable to retrieve AudioRecord pointer for getMetrics()");
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return (jobject) NULL;
    }

    // get what we have for the metrics from the record session
    mediametrics::Item *item = NULL;

    status_t err = lpRecord->getMetrics(item);
    if (err != OK) {
        ALOGE("getMetrics failed");
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return (jobject) NULL;
    }

    jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */);

    // housekeeping
    delete item;
    item = NULL;

    return mybundle;
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_active_microphones(JNIEnv *env,
        jobject thiz, jobject jActiveMicrophones) {
    if (jActiveMicrophones == NULL) {
        ALOGE("jActiveMicrophones is null");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }
    if (!env->IsInstanceOf(jActiveMicrophones, gArrayListClass)) {
        ALOGE("getActiveMicrophones not an arraylist");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for getActiveMicrophones()");
        return (jint)AUDIO_JAVA_ERROR;
    }

    jint jStatus = AUDIO_JAVA_SUCCESS;
    std::vector<media::MicrophoneInfoFw> activeMicrophones;
    status_t status = lpRecorder->getActiveMicrophones(&activeMicrophones);
    if (status != NO_ERROR) {
        ALOGE_IF(status != NO_ERROR, "AudioRecord::getActiveMicrophones error %d", status);
        jStatus = nativeToJavaStatus(status);
        return jStatus;
    }

    for (size_t i = 0; i < activeMicrophones.size(); i++) {
        jobject jMicrophoneInfo;
        jStatus = convertMicrophoneInfoFromNative(env, &jMicrophoneInfo, &activeMicrophones[i]);
        if (jStatus != AUDIO_JAVA_SUCCESS) {
            return jStatus;
        }
        env->CallBooleanMethod(jActiveMicrophones, gArrayListMethods.add, jMicrophoneInfo);
        env->DeleteLocalRef(jMicrophoneInfo);
    }
    return jStatus;
}

static int android_media_AudioRecord_set_preferred_microphone_direction(
                                JNIEnv *env, jobject thiz, jint direction) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for setPreferredMicrophoneDirection()");
        return (jint)AUDIO_JAVA_ERROR;
    }

    jint jStatus = AUDIO_JAVA_SUCCESS;
    status_t status = lpRecorder->setPreferredMicrophoneDirection(
                            static_cast<audio_microphone_direction_t>(direction));
    if (status != NO_ERROR) {
        jStatus = nativeToJavaStatus(status);
    }

    return jStatus;
}

static int android_media_AudioRecord_set_preferred_microphone_field_dimension(
                                JNIEnv *env, jobject thiz, jfloat zoom) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioRecord pointer for setPreferredMicrophoneFieldDimension()");
        return (jint)AUDIO_JAVA_ERROR;
    }

    jint jStatus = AUDIO_JAVA_SUCCESS;
    status_t status = lpRecorder->setPreferredMicrophoneFieldDimension(zoom);
    if (status != NO_ERROR) {
        jStatus = nativeToJavaStatus(status);
    }

    return jStatus;
}

static void android_media_AudioRecord_setLogSessionId(JNIEnv *env, jobject thiz,
                                                      jstring jlogSessionId) {
    sp<AudioRecord> record = getAudioRecord(env, thiz);
    if (record == nullptr) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "Unable to retrieve AudioRecord pointer for setLogSessionId()");
    }
    if (jlogSessionId == nullptr) {
        ALOGV("%s: logSessionId nullptr", __func__);
        record->setLogSessionId(nullptr);
        return;
    }
    ScopedUtfChars logSessionId(env, jlogSessionId);
    ALOGV("%s: logSessionId '%s'", __func__, logSessionId.c_str());
    record->setLogSessionId(logSessionId.c_str());
}

static jint android_media_AudioRecord_shareAudioHistory(JNIEnv *env, jobject thiz,
                                                        jstring jSharedPackageName,
                                                        jlong jSharedStartMs) {
    sp<AudioRecord> record = getAudioRecord(env, thiz);
    if (record == nullptr) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "Unable to retrieve AudioRecord pointer for setLogSessionId()");
    }
    if (jSharedPackageName == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "package name cannot be null");
    }
    ScopedUtfChars nSharedPackageName(env, jSharedPackageName);
    ALOGV("%s: nSharedPackageName '%s'", __func__, nSharedPackageName.c_str());
    return nativeToJavaStatus(record->shareAudioHistory(nSharedPackageName.c_str(),
                                                        static_cast<int64_t>(jSharedStartMs)));
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_port_id(JNIEnv *env,  jobject thiz) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "Unable to retrieve AudioRecord pointer for getId()");
        return (jint)AUDIO_PORT_HANDLE_NONE;
    }
    return (jint)lpRecorder->getPortId();
}


// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
        // name,               signature,  funcPtr
        {"native_start", "(II)I", (void *)android_media_AudioRecord_start},
        {"native_stop", "()V", (void *)android_media_AudioRecord_stop},
        {"native_setup",
         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I",
         (void *)android_media_AudioRecord_setup},
        {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
        {"native_release", "()V", (void *)android_media_AudioRecord_release},
        {"native_read_in_byte_array", "([BIIZ)I",
         (void *)android_media_AudioRecord_readInArray<jbyteArray>},
        {"native_read_in_short_array", "([SIIZ)I",
         (void *)android_media_AudioRecord_readInArray<jshortArray>},
        {"native_read_in_float_array", "([FIIZ)I",
         (void *)android_media_AudioRecord_readInArray<jfloatArray>},
        {"native_read_in_direct_buffer", "(Ljava/lang/Object;IZ)I",
         (void *)android_media_AudioRecord_readInDirectBuffer},
        {"native_get_buffer_size_in_frames", "()I",
         (void *)android_media_AudioRecord_get_buffer_size_in_frames},
        {"native_set_marker_pos", "(I)I", (void *)android_media_AudioRecord_set_marker_pos},
        {"native_get_marker_pos", "()I", (void *)android_media_AudioRecord_get_marker_pos},
        {"native_set_pos_update_period", "(I)I",
         (void *)android_media_AudioRecord_set_pos_update_period},
        {"native_get_pos_update_period", "()I",
         (void *)android_media_AudioRecord_get_pos_update_period},
        {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioRecord_get_min_buff_size},
        {"native_getMetrics", "()Landroid/os/PersistableBundle;",
         (void *)android_media_AudioRecord_native_getMetrics},
        {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice},
        {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId},
        {"native_enableDeviceCallback", "()V",
         (void *)android_media_AudioRecord_enableDeviceCallback},
        {"native_disableDeviceCallback", "()V",
         (void *)android_media_AudioRecord_disableDeviceCallback},
        {"native_get_timestamp", "(Landroid/media/AudioTimestamp;I)I",
         (void *)android_media_AudioRecord_get_timestamp},
        {"native_get_active_microphones", "(Ljava/util/ArrayList;)I",
         (void *)android_media_AudioRecord_get_active_microphones},
        {"native_getPortId", "()I", (void *)android_media_AudioRecord_get_port_id},
        {"native_set_preferred_microphone_direction", "(I)I",
         (void *)android_media_AudioRecord_set_preferred_microphone_direction},
        {"native_set_preferred_microphone_field_dimension", "(F)I",
         (void *)android_media_AudioRecord_set_preferred_microphone_field_dimension},
        {"native_setLogSessionId", "(Ljava/lang/String;)V",
         (void *)android_media_AudioRecord_setLogSessionId},
        {"native_shareAudioHistory", "(Ljava/lang/String;J)I",
         (void *)android_media_AudioRecord_shareAudioHistory},
};

// field names found in android/media/AudioRecord.java
#define JAVA_POSTEVENT_CALLBACK_NAME  "postEventFromNative"
#define JAVA_NATIVEAUDIORECORDERHANDLE_FIELD_NAME  "mNativeAudioRecordHandle"
#define JAVA_NATIVEJNIDATAHANDLE_FIELD_NAME        "mNativeJNIDataHandle"

// ----------------------------------------------------------------------------
int register_android_media_AudioRecord(JNIEnv *env)
{
    javaAudioRecordFields.postNativeEventInJava = NULL;
    javaAudioRecordFields.nativeRecorderInJavaObj = NULL;
    javaAudioRecordFields.jniData = NULL;


    // Get the AudioRecord class
    jclass audioRecordClass = FindClassOrDie(env, kClassPathName);
    // Get the postEvent method
    javaAudioRecordFields.postNativeEventInJava = GetStaticMethodIDOrDie(env,
            audioRecordClass, JAVA_POSTEVENT_CALLBACK_NAME,
            "(Ljava/lang/Object;IIILjava/lang/Object;)V");

    // Get the variables
    //    mNativeAudioRecordHandle
    javaAudioRecordFields.nativeRecorderInJavaObj = GetFieldIDOrDie(env,
            audioRecordClass, JAVA_NATIVEAUDIORECORDERHANDLE_FIELD_NAME, "J");
    //   mNativeJNIDataHandle
    javaAudioRecordFields.jniData = GetFieldIDOrDie(env,
            audioRecordClass, JAVA_NATIVEJNIDATAHANDLE_FIELD_NAME, "J");

    // Get the RecordTimestamp class and fields
    jclass audioTimestampClass = FindClassOrDie(env, "android/media/AudioTimestamp");
    javaAudioTimestampFields.fieldFramePosition =
            GetFieldIDOrDie(env, audioTimestampClass, "framePosition", "J");
    javaAudioTimestampFields.fieldNanoTime =
            GetFieldIDOrDie(env, audioTimestampClass, "nanoTime", "J");

    jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
    gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
    gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");

    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}

// ----------------------------------------------------------------------------
