static void GeofenceMonitorStatusCallback(
    int32_t status,
    uint32_t source,
    FlpLocation* lastLocation) {
  if(!IsValidCallbackThread()) {
    return;
  }

  jobject locationObject = NULL;
  if(lastLocation != NULL) {
    TranslateToObject(lastLocation, locationObject);
  }

  sCallbackEnv->CallVoidMethod(
      sCallbacksObj,
      sOnGeofenceMonitorStatus,
      status,
      source,
      locationObject
      );
  CheckExceptions(sCallbackEnv, __FUNCTION__);

  if(locationObject != NULL) {
    sCallbackEnv->DeleteLocalRef(locationObject);
  }
}
static void GeofenceTransitionCallback(
    int32_t geofenceId,
    FlpLocation* location,
    int32_t transition,
    FlpUtcTime timestamp,
    uint32_t sourcesUsed
    ) {
  if(!IsValidCallbackThread()) {
    return;
  }

  if(location == NULL) {
    ALOGE("GeofenceTransition received with invalid location: %p", location);
    return;
  }

  jobject locationObject = NULL;
  TranslateToObject(location, locationObject);

  sCallbackEnv->CallVoidMethod(
      sCallbacksObj,
      sOnGeofenceTransition,
      geofenceId,
      locationObject,
      transition,
      timestamp,
      sourcesUsed
      );
  CheckExceptions(sCallbackEnv, __FUNCTION__);

  if(locationObject != NULL) {
    sCallbackEnv->DeleteLocalRef(locationObject);
  }
}
static void LocationCallback(int32_t locationsCount, FlpLocation** locations) {
  if(!IsValidCallbackThread()) {
    return;
  }

  if(locationsCount == 0 || locations == NULL) {
    ALOGE(
        "Invalid LocationCallback. Count: %d, Locations: %p",
        locationsCount,
        locations
        );
    return;
  }

  jobjectArray locationsArray = NULL;
  TranslateToObjectArray(locationsCount, locations, locationsArray);

  sCallbackEnv->CallVoidMethod(
      sCallbacksObj,
      sOnLocationReport,
      locationsArray
      );
  CheckExceptions(sCallbackEnv, __FUNCTION__);

  if(locationsArray != NULL) {
    sCallbackEnv->DeleteLocalRef(locationsArray);
  }
}
static void GeofenceAddCallback(int32_t geofenceId, int32_t result) {
  if(!IsValidCallbackThread()) {
    return;
  }

  sCallbackEnv->CallVoidMethod(sCallbacksObj, sOnGeofenceAdd, geofenceId, result);
  CheckExceptions(sCallbackEnv, __FUNCTION__);
}
static void BatchingCapabilitiesCallback(int32_t capabilities) {
  if(!IsValidCallbackThread()) {
    return;
  }

  sCallbackEnv->CallVoidMethod(
      sCallbacksObj,
      sOnBatchingCapabilities,
      capabilities
      );
  CheckExceptions(sCallbackEnv, __FUNCTION__);
}
static void BatchingStatusCallback(int32_t status) {
  if(!IsValidCallbackThread()) {
    return;
  }

  sCallbackEnv->CallVoidMethod(
      sCallbacksObj,
      sOnBatchingStatus,
      status
      );
  CheckExceptions(sCallbackEnv, __FUNCTION__);
}
static int SetThreadEvent(ThreadEvent event) {
  JavaVM* javaVm = AndroidRuntime::getJavaVM();

  switch(event) {
    case ASSOCIATE_JVM:
    {
      if(sCallbackEnv != NULL) {
        ALOGE(
            "Attempted to associate callback in '%s'. Callback already associated.",
            __FUNCTION__
            );
        return FLP_RESULT_ERROR;
      }

      JavaVMAttachArgs args = {
          JNI_VERSION_1_6,
          "FLP Service Callback Thread",
          /* group */ NULL
      };

      jint attachResult = javaVm->AttachCurrentThread(&sCallbackEnv, &args);
      if (attachResult != 0) {
        ALOGE("Callback thread attachment error: %d", attachResult);
        return FLP_RESULT_ERROR;
      }

      ALOGV("Callback thread attached: %p", sCallbackEnv);
      break;
    }
    case DISASSOCIATE_JVM:
    {
      if (!IsValidCallbackThread()) {
        ALOGE(
            "Attempted to dissasociate an unnownk callback thread : '%s'.",
            __FUNCTION__
            );
        return FLP_RESULT_ERROR;
      }

      if (javaVm->DetachCurrentThread() != 0) {
        return FLP_RESULT_ERROR;
      }

      sCallbackEnv = NULL;
      break;
    }
    default:
      ALOGE("Invalid ThreadEvent request %d", event);
      return FLP_RESULT_ERROR;
  }

  return FLP_RESULT_SUCCESS;
}
static int SetThreadEvent(ThreadEvent event) {
  JavaVM* javaVm = AndroidRuntime::getJavaVM();

  switch(event) {
    case ASSOCIATE_JVM:
    {
      if(sCallbackEnv != NULL) {
        ALOGE(
            "Attempted to associate callback in '%s'. Callback already associated.",
            __FUNCTION__
            );
        return FLP_RESULT_ERROR;
      }

      JavaVMAttachArgs args = {
          JNI_VERSION_1_6,
          "FLP Service Callback Thread",
          /* group */ NULL
      };

      jint attachResult = javaVm->AttachCurrentThread(&sCallbackEnv, &args);
      if (attachResult != 0) {
        ALOGE("Callback thread attachment error: %d", attachResult);
        return FLP_RESULT_ERROR;
      }

      ALOGV("Callback thread attached: %p", sCallbackEnv);

      // Send the version to the upper layer.
      sCallbackEnv->CallVoidMethod(
            sCallbacksObj,
            sSetVersion,
            sFlpInterface->size == sizeof(FlpLocationInterface) ? 2 : 1
            );
      CheckExceptions(sCallbackEnv, __FUNCTION__);
      break;
    }
    case DISASSOCIATE_JVM:
    {
      if (!IsValidCallbackThread()) {
        ALOGE(
            "Attempted to dissasociate an unnownk callback thread : '%s'.",
            __FUNCTION__
            );
        return FLP_RESULT_ERROR;
      }

      if (javaVm->DetachCurrentThread() != 0) {
        return FLP_RESULT_ERROR;
      }

      sCallbackEnv = NULL;
      break;
    }
    default:
      ALOGE("Invalid ThreadEvent request %d", event);
      return FLP_RESULT_ERROR;
  }

  return FLP_RESULT_SUCCESS;
}