/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

/*
 *  Android audio device implementation (JNI/AudioRecord usage)
 */

// TODO(xians): Break out attach and detach current thread to JVM to
// separate functions.

#include "webrtc/modules/audio_device/android/audio_record_jni.h"

#include <android/log.h>
#include <stdlib.h>

#include "webrtc/modules/audio_device/android/audio_common.h"
#include "webrtc/modules/audio_device/audio_device_config.h"
#include "webrtc/modules/audio_device/audio_device_utility.h"

#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
#include "webrtc/system_wrappers/interface/trace.h"

namespace webrtc {

JavaVM* AudioRecordJni::globalJvm = NULL;
JNIEnv* AudioRecordJni::globalJNIEnv = NULL;
jobject AudioRecordJni::globalContext = NULL;
jclass AudioRecordJni::globalScClass = NULL;

int32_t AudioRecordJni::SetAndroidAudioDeviceObjects(void* javaVM, void* env,
                                                     void* context) {
  assert(env);
  globalJvm = reinterpret_cast<JavaVM*>(javaVM);
  globalJNIEnv = reinterpret_cast<JNIEnv*>(env);
  // Get java class type (note path to class packet).
  jclass javaScClassLocal = globalJNIEnv->FindClass(
      "org/webrtc/voiceengine/WebRtcAudioRecord");
  if (!javaScClassLocal) {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
                 "%s: could not find java class", __FUNCTION__);
    return -1; // exception thrown
  }

  // Create a global reference to the class (to tell JNI that we are
  // referencing it after this function has returned).
  globalScClass = reinterpret_cast<jclass> (
      globalJNIEnv->NewGlobalRef(javaScClassLocal));
  if (!globalScClass) {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
                 "%s: could not create reference", __FUNCTION__);
    return -1;
  }

  globalContext = globalJNIEnv->NewGlobalRef(
      reinterpret_cast<jobject>(context));
  if (!globalContext) {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
                 "%s: could not create context reference", __FUNCTION__);
    return -1;
  }

  // Delete local class ref, we only use the global ref
  globalJNIEnv->DeleteLocalRef(javaScClassLocal);

  return 0;
}

void AudioRecordJni::ClearAndroidAudioDeviceObjects() {
  WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, -1,
               "%s: env is NULL, assuming deinit", __FUNCTION__);

  globalJvm = NULL;;
  if (!globalJNIEnv) {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, -1,
                 "%s: saved env already NULL", __FUNCTION__);
    return;
  }

  globalJNIEnv->DeleteGlobalRef(globalContext);
  globalContext = reinterpret_cast<jobject>(NULL);

  globalJNIEnv->DeleteGlobalRef(globalScClass);
  globalScClass = reinterpret_cast<jclass>(NULL);

  globalJNIEnv = reinterpret_cast<JNIEnv*>(NULL);
}

AudioRecordJni::AudioRecordJni(
    const int32_t id, PlayoutDelayProvider* delay_provider)
    : _javaVM(NULL),
      _jniEnvRec(NULL),
      _javaScClass(0),
      _javaScObj(0),
      _javaRecBuffer(0),
      _javaDirectRecBuffer(NULL),
      _javaMidRecAudio(0),
      _ptrAudioBuffer(NULL),
      _critSect(*CriticalSectionWrapper::CreateCriticalSection()),
      _id(id),
      _delay_provider(delay_provider),
      _initialized(false),
      _timeEventRec(*EventWrapper::Create()),
      _recStartStopEvent(*EventWrapper::Create()),
      _ptrThreadRec(NULL),
      _recThreadID(0),
      _recThreadIsInitialized(false),
      _shutdownRecThread(false),
      _recordingDeviceIsSpecified(false),
      _recording(false),
      _recIsInitialized(false),
      _micIsInitialized(false),
      _startRec(false),
      _recWarning(0),
      _recError(0),
      _delayRecording(0),
      _AGC(false),
      _samplingFreqIn((N_REC_SAMPLES_PER_SEC/1000)),
      _recAudioSource(1) { // 1 is AudioSource.MIC which is our default
  memset(_recBuffer, 0, sizeof(_recBuffer));
}

AudioRecordJni::~AudioRecordJni() {
  WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id,
               "%s destroyed", __FUNCTION__);

  Terminate();

  delete &_recStartStopEvent;
  delete &_timeEventRec;
  delete &_critSect;
}

int32_t AudioRecordJni::Init() {
  CriticalSectionScoped lock(&_critSect);

  if (_initialized)
  {
    return 0;
  }

  _recWarning = 0;
  _recError = 0;

  // Init Java member variables
  // and set up JNI interface to
  // AudioDeviceAndroid java class
  if (InitJavaResources() != 0)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: Failed to init Java resources", __FUNCTION__);
    return -1;
  }

  // Check the sample rate to be used for playback and recording
  // and the max playout volume
  if (InitSampleRate() != 0)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: Failed to init samplerate", __FUNCTION__);
    return -1;
  }

  const char* threadName = "jni_audio_capture_thread";
  _ptrThreadRec = ThreadWrapper::CreateThread(RecThreadFunc, this,
                                              kRealtimePriority, threadName);
  if (_ptrThreadRec == NULL)
  {
    WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                 "  failed to create the rec audio thread");
    return -1;
  }

  unsigned int threadID(0);
  if (!_ptrThreadRec->Start(threadID))
  {
    WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                 "  failed to start the rec audio thread");
    delete _ptrThreadRec;
    _ptrThreadRec = NULL;
    return -1;
  }
  _recThreadID = threadID;
  _initialized = true;

  return 0;
}

int32_t AudioRecordJni::Terminate() {
  CriticalSectionScoped lock(&_critSect);

  if (!_initialized)
  {
    return 0;
  }

  StopRecording();
  _shutdownRecThread = true;
  _timeEventRec.Set(); // Release rec thread from waiting state
  if (_ptrThreadRec)
  {
    // First, the thread must detach itself from Java VM
    _critSect.Leave();
    if (kEventSignaled != _recStartStopEvent.Wait(5000))
    {
      WEBRTC_TRACE(
          kTraceError,
          kTraceAudioDevice,
          _id,
          "%s: Recording thread shutdown timed out, cannot "
          "terminate thread",
          __FUNCTION__);
      // If we close thread anyway, the app will crash
      return -1;
    }
    _recStartStopEvent.Reset();
    _critSect.Enter();

    // Close down rec thread
    ThreadWrapper* tmpThread = _ptrThreadRec;
    _ptrThreadRec = NULL;
    _critSect.Leave();
    tmpThread->SetNotAlive();
    // Release again, we might have returned to waiting state
    _timeEventRec.Set();
    if (tmpThread->Stop())
    {
      delete tmpThread;
      _jniEnvRec = NULL;
    }
    else
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "  failed to close down the rec audio thread");
    }
    _critSect.Enter();

    _recThreadIsInitialized = false;
  }
  _micIsInitialized = false;
  _recordingDeviceIsSpecified = false;

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "%s: Could not attach thread to JVM (%d, %p)",
                   __FUNCTION__, res, env);
      return -1;
    }
    isAttached = true;
  }

  // Make method IDs and buffer pointers unusable
  _javaMidRecAudio = 0;
  _javaDirectRecBuffer = NULL;

  // Delete the references to the java buffers, this allows the
  // garbage collector to delete them
  env->DeleteGlobalRef(_javaRecBuffer);
  _javaRecBuffer = 0;

  // Delete the references to the java object and class, this allows the
  // garbage collector to delete them
  env->DeleteGlobalRef(_javaScObj);
  _javaScObj = 0;
  _javaScClass = 0;

  // Detach this thread if it was attached
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "%s: Could not detach thread from JVM", __FUNCTION__);
    }
  }

  _initialized = false;

  return 0;
}

int32_t AudioRecordJni::RecordingDeviceName(uint16_t index,
                                            char name[kAdmMaxDeviceNameSize],
                                            char guid[kAdmMaxGuidSize]) {
  if (0 != index)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Device index is out of range [0,0]");
    return -1;
  }

  // Return empty string
  memset(name, 0, kAdmMaxDeviceNameSize);

  if (guid)
  {
    memset(guid, 0, kAdmMaxGuidSize);
  }

  return 0;
}

int32_t AudioRecordJni::SetRecordingDevice(uint16_t index) {
  if (_recIsInitialized)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Recording already initialized");
    return -1;
  }

  // Recording device index will be used for specifying recording
  // audio source, allow any value
  _recAudioSource = index;
  _recordingDeviceIsSpecified = true;

  return 0;
}

int32_t AudioRecordJni::SetRecordingDevice(
    AudioDeviceModule::WindowsDeviceType device) {
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::RecordingIsAvailable(bool& available) {  // NOLINT
  available = false;

  // Try to initialize the playout side
  int32_t res = InitRecording();

  // Cancel effect of initialization
  StopRecording();

  if (res != -1)
  {
    available = true;
  }

  return res;
}

int32_t AudioRecordJni::InitRecording() {
  CriticalSectionScoped lock(&_critSect);

  if (!_initialized)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Not initialized");
    return -1;
  }

  if (_recording)
  {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  Recording already started");
    return -1;
  }

  if (!_recordingDeviceIsSpecified)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Recording device is not specified");
    return -1;
  }

  if (_recIsInitialized)
  {
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  Recording already initialized");
    return 0;
  }

  // Initialize the microphone
  if (InitMicrophone() == -1)
  {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  InitMicrophone() failed");
  }

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "  Could not attach thread to JVM (%d, %p)", res, env);
      return -1;
    }
    isAttached = true;
  }

  // get the method ID
  jmethodID initRecordingID = env->GetMethodID(_javaScClass, "InitRecording",
                                               "(II)I");

  int samplingFreq = 44100;
  if (_samplingFreqIn != 44)
  {
    samplingFreq = _samplingFreqIn * 1000;
  }

  int retVal = -1;

  // call java sc object method
  jint res = env->CallIntMethod(_javaScObj, initRecordingID, _recAudioSource,
                                samplingFreq);
  if (res < 0)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "InitRecording failed (%d)", res);
  }
  else
  {
    // Set the audio device buffer sampling rate
    _ptrAudioBuffer->SetRecordingSampleRate(_samplingFreqIn * 1000);

    // the init rec function returns a fixed delay
    _delayRecording = res / _samplingFreqIn;

    _recIsInitialized = true;
    retVal = 0;
  }

  // Detach this thread if it was attached
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "  Could not detach thread from JVM");
    }
  }

  return retVal;
}

int32_t AudioRecordJni::StartRecording() {
  CriticalSectionScoped lock(&_critSect);

  if (!_recIsInitialized)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Recording not initialized");
    return -1;
  }

  if (_recording)
  {
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  Recording already started");
    return 0;
  }

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "  Could not attach thread to JVM (%d, %p)", res, env);
      return -1;
    }
    isAttached = true;
  }

  // get the method ID
  jmethodID startRecordingID = env->GetMethodID(_javaScClass,
                                                "StartRecording", "()I");

  // Call java sc object method
  jint res = env->CallIntMethod(_javaScObj, startRecordingID);
  if (res < 0)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "StartRecording failed (%d)", res);
    return -1;
  }

  _recWarning = 0;
  _recError = 0;

  // Signal to recording thread that we want to start
  _startRec = true;
  _timeEventRec.Set(); // Release thread from waiting state
  _critSect.Leave();
  // Wait for thread to init
  if (kEventSignaled != _recStartStopEvent.Wait(5000))
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Timeout or error starting");
  }
  _recStartStopEvent.Reset();
  _critSect.Enter();

  // Detach this thread if it was attached
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "  Could not detach thread from JVM");
    }
  }

  return 0;

}

int32_t AudioRecordJni::StopRecording() {
  CriticalSectionScoped lock(&_critSect);

  if (!_recIsInitialized)
  {
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "  Recording is not initialized");
    return 0;
  }

  // make sure we don't start recording (it's asynchronous),
  // assuming that we are under lock
  _startRec = false;

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "  Could not attach thread to JVM (%d, %p)", res, env);
      return -1;
    }
    isAttached = true;
  }

  // get the method ID
  jmethodID stopRecordingID = env->GetMethodID(_javaScClass, "StopRecording",
                                               "()I");

  // Call java sc object method
  jint res = env->CallIntMethod(_javaScObj, stopRecordingID);
  if (res < 0)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "StopRecording failed (%d)", res);
  }

  _recIsInitialized = false;
  _recording = false;
  _recWarning = 0;
  _recError = 0;

  // Detach this thread if it was attached
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "  Could not detach thread from JVM");
    }
  }

  return 0;

}

int32_t AudioRecordJni::SetAGC(bool enable) {
  _AGC = enable;
  return 0;
}

int32_t AudioRecordJni::InitMicrophone() {
  CriticalSectionScoped lock(&_critSect);

  if (_recording)
  {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "  Recording already started");
    return -1;
  }

  if (!_recordingDeviceIsSpecified)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Recording device is not specified");
    return -1;
  }

  // Nothing needs to be done here, we use a flag to have consistent
  // behavior with other platforms
  _micIsInitialized = true;

  return 0;
}

int32_t AudioRecordJni::MicrophoneVolumeIsAvailable(
    bool& available) {  // NOLINT
  available = false;  // Mic volume not supported on Android
  return 0;
}

int32_t AudioRecordJni::SetMicrophoneVolume( uint32_t /*volume*/) {

  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MicrophoneVolume(uint32_t& volume) const {  // NOLINT
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MaxMicrophoneVolume(
    uint32_t& maxVolume) const {  // NOLINT
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MinMicrophoneVolume(
    uint32_t& minVolume) const {  // NOLINT
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MicrophoneVolumeStepSize(
    uint16_t& stepSize) const {
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MicrophoneMuteIsAvailable(bool& available) {  // NOLINT
  available = false; // Mic mute not supported on Android
  return 0;
}

int32_t AudioRecordJni::SetMicrophoneMute(bool enable) {
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MicrophoneMute(bool& enabled) const {  // NOLINT
  WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
               "  API call not supported on this platform");
  return -1;
}

int32_t AudioRecordJni::MicrophoneBoostIsAvailable(bool& available) {  // NOLINT
  available = false; // Mic boost not supported on Android
  return 0;
}

int32_t AudioRecordJni::SetMicrophoneBoost(bool enable) {
  if (!_micIsInitialized)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Microphone not initialized");
    return -1;
  }

  if (enable)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Enabling not available");
    return -1;
  }

  return 0;
}

int32_t AudioRecordJni::MicrophoneBoost(bool& enabled) const {  // NOLINT
  if (!_micIsInitialized)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Microphone not initialized");
    return -1;
  }

  enabled = false;

  return 0;
}

int32_t AudioRecordJni::StereoRecordingIsAvailable(bool& available) {  // NOLINT
  available = false; // Stereo recording not supported on Android
  return 0;
}

int32_t AudioRecordJni::SetStereoRecording(bool enable) {
  if (enable)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Enabling not available");
    return -1;
  }

  return 0;
}

int32_t AudioRecordJni::StereoRecording(bool& enabled) const {  // NOLINT
  enabled = false;
  return 0;
}

int32_t AudioRecordJni::RecordingDelay(uint16_t& delayMS) const {  // NOLINT
  delayMS = _delayRecording;
  return 0;
}

bool AudioRecordJni::RecordingWarning() const {
  return (_recWarning > 0);
}

bool AudioRecordJni::RecordingError() const {
  return (_recError > 0);
}

void AudioRecordJni::ClearRecordingWarning() {
  _recWarning = 0;
}

void AudioRecordJni::ClearRecordingError() {
  _recError = 0;
}

void AudioRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
  CriticalSectionScoped lock(&_critSect);
  _ptrAudioBuffer = audioBuffer;
  // inform the AudioBuffer about default settings for this implementation
  _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
  _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS);
}

int32_t AudioRecordJni::SetRecordingSampleRate(const uint32_t samplesPerSec) {
  if (samplesPerSec > 48000 || samplesPerSec < 8000)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "  Invalid sample rate");
    return -1;
  }

  // set the recording sample rate to use
  if (samplesPerSec == 44100)
  {
    _samplingFreqIn = 44;
  }
  else
  {
    _samplingFreqIn = samplesPerSec / 1000;
  }

  // Update the AudioDeviceBuffer
  _ptrAudioBuffer->SetRecordingSampleRate(samplesPerSec);

  return 0;
}

int32_t AudioRecordJni::InitJavaResources() {
  // todo: Check if we already have created the java object
  _javaVM = globalJvm;
  _javaScClass = globalScClass;

  // use the jvm that has been set
  if (!_javaVM)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: Not a valid Java VM pointer", __FUNCTION__);
    return -1;
  }

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "%s: Could not attach thread to JVM (%d, %p)",
                   __FUNCTION__, res, env);
      return -1;
    }
    isAttached = true;
  }

  WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
               "get method id");

  // get the method ID for the void(void) constructor
  jmethodID cid = env->GetMethodID(_javaScClass, "<init>", "()V");
  if (cid == NULL)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get constructor ID", __FUNCTION__);
    return -1; /* exception thrown */
  }

  WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
               "construct object", __FUNCTION__);

  // construct the object
  jobject javaScObjLocal = env->NewObject(_javaScClass, cid);
  if (!javaScObjLocal)
  {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "%s: could not create Java sc object", __FUNCTION__);
    return -1;
  }

  // Create a reference to the object (to tell JNI that we are referencing it
  // after this function has returned).
  _javaScObj = env->NewGlobalRef(javaScObjLocal);
  if (!_javaScObj)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not create Java sc object reference",
                 __FUNCTION__);
    return -1;
  }

  // Delete local object ref, we only use the global ref.
  env->DeleteLocalRef(javaScObjLocal);

  //////////////////////
  // AUDIO MANAGEMENT

  // This is not mandatory functionality
  if (globalContext) {
    jfieldID context_id = env->GetFieldID(globalScClass,
                                          "_context",
                                          "Landroid/content/Context;");
    if (!context_id) {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "%s: could not get _context id", __FUNCTION__);
      return -1;
    }

    env->SetObjectField(_javaScObj, context_id, globalContext);
    jobject javaContext = env->GetObjectField(_javaScObj, context_id);
    if (!javaContext) {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "%s: could not set or get _context", __FUNCTION__);
      return -1;
    }
  }
  else {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "%s: did not set Context - some functionality is not "
                 "supported",
                 __FUNCTION__);
  }

  // Get rec buffer field ID.
  jfieldID fidRecBuffer = env->GetFieldID(_javaScClass, "_recBuffer",
                                          "Ljava/nio/ByteBuffer;");
  if (!fidRecBuffer)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get rec buffer fid", __FUNCTION__);
    return -1;
  }

  // Get rec buffer object.
  jobject javaRecBufferLocal = env->GetObjectField(_javaScObj, fidRecBuffer);
  if (!javaRecBufferLocal)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get rec buffer", __FUNCTION__);
    return -1;
  }

  // Create a global reference to the object (to tell JNI that we are
  // referencing it after this function has returned)
  // NOTE: we are referencing it only through the direct buffer (see below).
  _javaRecBuffer = env->NewGlobalRef(javaRecBufferLocal);
  if (!_javaRecBuffer)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get rec buffer reference", __FUNCTION__);
    return -1;
  }

  // Delete local object ref, we only use the global ref.
  env->DeleteLocalRef(javaRecBufferLocal);

  // Get direct buffer.
  _javaDirectRecBuffer = env->GetDirectBufferAddress(_javaRecBuffer);
  if (!_javaDirectRecBuffer)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get direct rec buffer", __FUNCTION__);
    return -1;
  }

  // Get the rec audio method ID.
  _javaMidRecAudio = env->GetMethodID(_javaScClass, "RecordAudio", "(I)I");
  if (!_javaMidRecAudio)
  {
    WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                 "%s: could not get rec audio mid", __FUNCTION__);
    return -1;
  }

  // Detach this thread if it was attached.
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "%s: Could not detach thread from JVM", __FUNCTION__);
    }
  }

  return 0;

}

int32_t AudioRecordJni::InitSampleRate() {
  int samplingFreq = 44100;
  jint res = 0;

  // get the JNI env for this thread
  JNIEnv *env;
  bool isAttached = false;

  // get the JNI env for this thread
  if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
  {
    // try to attach the thread and get the env
    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&env, NULL);
    if ((res < 0) || !env)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "%s: Could not attach thread to JVM (%d, %p)",
                   __FUNCTION__, res, env);
      return -1;
    }
    isAttached = true;
  }

  if (_samplingFreqIn > 0)
  {
    // read the configured sampling rate
    samplingFreq = 44100;
    if (_samplingFreqIn != 44)
    {
      samplingFreq = _samplingFreqIn * 1000;
    }
    WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id,
                 "  Trying configured recording sampling rate %d",
                 samplingFreq);
  }

  // get the method ID
  jmethodID initRecordingID = env->GetMethodID(_javaScClass, "InitRecording",
                                               "(II)I");

  bool keepTrying = true;
  while (keepTrying)
  {
    // call java sc object method
    res = env->CallIntMethod(_javaScObj, initRecordingID, _recAudioSource,
                             samplingFreq);
    if (res < 0)
    {
      switch (samplingFreq)
      {
        case 44100:
          samplingFreq = 16000;
          break;
        case 16000:
          samplingFreq = 8000;
          break;
        default: // error
          WEBRTC_TRACE(kTraceError,
                       kTraceAudioDevice, _id,
                       "%s: InitRecording failed (%d)", __FUNCTION__,
                       res);
          return -1;
      }
    }
    else
    {
      keepTrying = false;
    }
  }

  // set the recording sample rate to use
  if (samplingFreq == 44100)
  {
    _samplingFreqIn = 44;
  }
  else
  {
    _samplingFreqIn = samplingFreq / 1000;
  }

  WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id,
               "Recording sample rate set to (%d)", _samplingFreqIn);

  // get the method ID
  jmethodID stopRecordingID = env->GetMethodID(_javaScClass, "StopRecording",
                                               "()I");

  // Call java sc object method
  res = env->CallIntMethod(_javaScObj, stopRecordingID);
  if (res < 0)
  {
    WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                 "StopRecording failed (%d)", res);
  }

  // Detach this thread if it was attached
  if (isAttached)
  {
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
                   "%s: Could not detach thread from JVM", __FUNCTION__);
    }
  }

  return 0;
}

bool AudioRecordJni::RecThreadFunc(void* pThis)
{
  return (static_cast<AudioRecordJni*> (pThis)->RecThreadProcess());
}

bool AudioRecordJni::RecThreadProcess()
{
  if (!_recThreadIsInitialized)
  {
    // Do once when thread is started

    // Attach this thread to JVM
    jint res = _javaVM->AttachCurrentThread(&_jniEnvRec, NULL);

    // Get the JNI env for this thread
    if ((res < 0) || !_jniEnvRec)
    {
      WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice,
                   _id, "Could not attach rec thread to JVM (%d, %p)",
                   res, _jniEnvRec);
      return false; // Close down thread
    }

    _recThreadIsInitialized = true;
  }

  // just sleep if rec has not started
  if (!_recording)
  {
    switch (_timeEventRec.Wait(1000))
    {
      case kEventSignaled:
        WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice,
                     _id, "Recording thread event signal");
        _timeEventRec.Reset();
        break;
      case kEventError:
        WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice,
                     _id, "Recording thread event error");
        return true;
      case kEventTimeout:
        WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice,
                     _id, "Recording thread event timeout");
        return true;
    }
  }

  Lock();

  if (_startRec)
  {
    WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
                 "_startRec true, performing initial actions");
    _startRec = false;
    _recording = true;
    _recWarning = 0;
    _recError = 0;
    _recStartStopEvent.Set();
  }

  if (_recording)
  {
    uint32_t samplesToRec = _samplingFreqIn * 10;

    // Call java sc object method to record data to direct buffer
    // Will block until data has been recorded (see java sc class),
    // therefore we must release the lock
    UnLock();
    jint recDelayInSamples = _jniEnvRec->CallIntMethod(_javaScObj,
                                                        _javaMidRecAudio,
                                                        2 * samplesToRec);
    if (recDelayInSamples < 0)
    {
      WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                   "RecordAudio failed");
      _recWarning = 1;
    }
    else
    {
      _delayRecording = recDelayInSamples / _samplingFreqIn;
    }
    Lock();

    // Check again since recording may have stopped during Java call
    if (_recording)
    {
      //            WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
      //                         "total delay is %d", msPlayDelay + _delayRecording);

      // Copy data to our direct buffer (held by java sc object)
      // todo: Give _javaDirectRecBuffer directly to VoE?
      // todo: Check count <= 480 ?
      memcpy(_recBuffer, _javaDirectRecBuffer, 2 * samplesToRec);

      // store the recorded buffer (no action will be taken if the
      // #recorded samples is not a full buffer)
      _ptrAudioBuffer->SetRecordedBuffer(_recBuffer, samplesToRec);

      // store vqe delay values
      _ptrAudioBuffer->SetVQEData(_delay_provider->PlayoutDelayMs(),
                                  _delayRecording, 0);

      // deliver recorded samples at specified sample rate, mic level
      // etc. to the observer using callback
      UnLock();
      _ptrAudioBuffer->DeliverRecordedData();
      Lock();
    }

  }  // _recording

  if (_shutdownRecThread)
  {
    WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                 "Detaching rec thread from Java VM");

    // Detach thread from Java VM
    if (_javaVM->DetachCurrentThread() < 0)
    {
      WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice,
                   _id, "Could not detach recording thread from JVM");
      _shutdownRecThread = false;
      // If we say OK (i.e. set event) and close thread anyway,
      // app will crash
    }
    else
    {
      _jniEnvRec = NULL;
      _shutdownRecThread = false;
      _recStartStopEvent.Set(); // Signal to Terminate() that we are done

      WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
                   "Sent signal rec");
    }
  }

  UnLock();
  return true;
}

}  // namespace webrtc
