Add FlpHal layer to support Location Batching.

Change-Id: Ia3a57d869dfb3f067a1b95fa66d54f311ddcfdc3
diff --git a/services/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/jni/com_android_server_location_FlpHardwareProvider.cpp
new file mode 100644
index 0000000..48b86db
--- /dev/null
+++ b/services/jni/com_android_server_location_FlpHardwareProvider.cpp
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2013 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/license/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_TAG "FuseLocationProvider"
+#define LOG_NDEBUG  0
+
+#define WAKE_LOCK_NAME  "FLP"
+#define LOCATION_CLASS_NAME "android/location/Location"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "hardware/fused_location.h"
+#include "hardware_legacy/power.h"
+
+static jobject sCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+static hw_device_t* sHardwareDevice = NULL;
+
+static jmethodID sOnLocationReport = NULL;
+static jmethodID sOnDataReport = NULL;
+static jmethodID sOnGeofenceTransition = NULL;
+static jmethodID sOnGeofenceMonitorStatus = NULL;
+static jmethodID sOnGeofenceAdd = NULL;
+static jmethodID sOnGeofenceRemove = NULL;
+static jmethodID sOnGeofencePause = NULL;
+static jmethodID sOnGeofenceResume = NULL;
+
+static const FlpLocationInterface* sFlpInterface = NULL;
+static const FlpDiagnosticInterface* sFlpDiagnosticInterface = NULL;
+static const FlpGeofencingInterface* sFlpGeofencingInterface = NULL;
+static const FlpDeviceContextInterface* sFlpDeviceContextInterface = NULL;
+
+namespace android {
+
+static inline void CheckExceptions(JNIEnv* env, const char* methodName) {
+  if(!env->ExceptionCheck()) {
+    return;
+  }
+
+  ALOGE("An exception was thrown by '%s'.", methodName);
+  LOGE_EX(env);
+  env->ExceptionClear();
+}
+
+static inline void ThrowOnError(
+    JNIEnv* env,
+    int resultCode,
+    const char* methodName) {
+  if(resultCode == FLP_RESULT_SUCCESS) {
+    return;
+  }
+
+  ALOGE("Error %d in '%s'", resultCode, methodName);
+  jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
+  env->ThrowNew(exceptionClass, methodName);
+}
+
+static bool IsValidCallbackThread() {
+  JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+  if(sCallbackEnv == NULL || sCallbackEnv != env) {
+    ALOGE("CallbackThread check fail: env=%p, expected=%p", env, sCallbackEnv);
+    return false;
+  }
+
+  return true;
+}
+
+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;
+}
+
+/*
+ * Initializes the FlpHardwareProvider class from the native side by opening
+ * the HW module and obtaining the proper interfaces.
+ */
+static void ClassInit(JNIEnv* env, jclass clazz) {
+  // get references to the Java provider methods
+  sOnLocationReport = env->GetMethodID(
+      clazz,
+      "onLocationReport",
+      "([Landroid/location/Location;)V");
+  sOnDataReport = env->GetMethodID(
+      clazz,
+      "onDataReport",
+      "(Ljava/lang/String;)V"
+      );
+  sOnGeofenceTransition = env->GetMethodID(
+      clazz,
+      "onGeofenceTransition",
+      "(ILandroid/location/Location;IJI)V"
+      );
+  sOnGeofenceMonitorStatus = env->GetMethodID(
+      clazz,
+      "onGeofenceMonitorStatus",
+      "(IILandroid/location/Location;)V"
+      );
+  sOnGeofenceAdd = env->GetMethodID(clazz, "onGeofenceAdd", "(II)V");
+  sOnGeofenceRemove = env->GetMethodID(clazz, "onGeofenceRemove", "(II)V");
+  sOnGeofencePause = env->GetMethodID(clazz, "onGeofencePause", "(II)V");
+  sOnGeofenceResume = env->GetMethodID(clazz, "onGeofenceResume", "(II)V");
+}
+
+/*
+ * Helper function to unwrap a java object back into a FlpLocation structure.
+ */
+static void TranslateFromObject(
+    JNIEnv* env,
+    jobject locationObject,
+    FlpLocation& location) {
+  location.size = sizeof(FlpLocation);
+  location.flags = 0;
+
+  jclass locationClass = env->GetObjectClass(locationObject);
+
+  jmethodID getLatitude = env->GetMethodID(locationClass, "getLatitude", "()D");
+  location.latitude = env->CallDoubleMethod(locationObject, getLatitude);
+  jmethodID getLongitude = env->GetMethodID(locationClass, "getLongitude", "()D");
+  location.longitude = env->CallDoubleMethod(locationObject, getLongitude);
+  jmethodID getTime = env->GetMethodID(locationClass, "getTime", "()J");
+  location.timestamp = env->CallLongMethod(locationObject, getTime);
+  location.flags |= FLP_LOCATION_HAS_LAT_LONG;
+
+  jmethodID hasAltitude = env->GetMethodID(locationClass, "hasAltitude", "()Z");
+  if (env->CallBooleanMethod(locationObject, hasAltitude)) {
+    jmethodID getAltitude = env->GetMethodID(locationClass, "getAltitude", "()D");
+    location.altitude = env->CallDoubleMethod(locationObject, getAltitude);
+    location.flags |= FLP_LOCATION_HAS_ALTITUDE;
+  }
+
+  jmethodID hasSpeed = env->GetMethodID(locationClass, "hasSpeed", "()Z");
+  if (env->CallBooleanMethod(locationObject, hasSpeed)) {
+    jmethodID getSpeed = env->GetMethodID(locationClass, "getSpeed", "()F");
+    location.speed = env->CallFloatMethod(locationObject, getSpeed);
+    location.flags |= FLP_LOCATION_HAS_SPEED;
+  }
+
+  jmethodID hasBearing = env->GetMethodID(locationClass, "hasBearing", "()Z");
+  if (env->CallBooleanMethod(locationObject, hasBearing)) {
+    jmethodID getBearing = env->GetMethodID(locationClass, "getBearing", "()F");
+    location.bearing = env->CallFloatMethod(locationObject, getBearing);
+    location.flags |= FLP_LOCATION_HAS_BEARING;
+  }
+
+  jmethodID hasAccuracy = env->GetMethodID(locationClass, "hasAccuracy", "()Z");
+  if (env->CallBooleanMethod(locationObject, hasAccuracy)) {
+    jmethodID getAccuracy = env->GetMethodID(
+        locationClass,
+        "getAccuracy",
+        "()F"
+        );
+    location.accuracy = env->CallFloatMethod(locationObject, getAccuracy);
+    location.flags |= FLP_LOCATION_HAS_ACCURACY;
+  }
+
+  // TODO: wire sources_used if Location class exposes them
+}
+
+/*
+ * Helper function to unwrap FlpBatchOptions from the Java Runtime calls.
+ */
+static void TranslateFromObject(
+    JNIEnv* env,
+    jobject batchOptionsObject,
+    FlpBatchOptions& batchOptions) {
+  jclass batchOptionsClass = env->GetObjectClass(batchOptionsObject);
+
+  jmethodID getMaxPower = env->GetMethodID(
+      batchOptionsClass,
+      "getMaxPowerAllocationInMW",
+      "()D"
+      );
+  batchOptions.max_power_allocation_mW = env->CallDoubleMethod(
+      batchOptionsObject,
+      getMaxPower
+      );
+
+  jmethodID getPeriod = env->GetMethodID(
+      batchOptionsClass,
+      "getPeriodInNS",
+      "()J"
+      );
+  batchOptions.period_ns = env->CallLongMethod(batchOptionsObject, getPeriod);
+
+  jmethodID getSourcesToUse = env->GetMethodID(
+      batchOptionsClass,
+      "getSourcesToUse",
+      "()I"
+      );
+  batchOptions.sources_to_use = env->CallIntMethod(
+      batchOptionsObject,
+      getSourcesToUse
+      );
+
+  jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I");
+  batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags);
+}
+
+/*
+ * Helper function to transform FlpLocation into a java object.
+ */
+static void TranslateToObject(const FlpLocation* location, jobject& locationObject) {
+  jclass locationClass = sCallbackEnv->FindClass(LOCATION_CLASS_NAME);
+  jmethodID locationCtor = sCallbackEnv->GetMethodID(
+      locationClass,
+      "<init>",
+      "(Ljava/lang/String;)V"
+      );
+
+  // the provider is set in the upper JVM layer
+  locationObject = sCallbackEnv->NewObject(locationClass, locationCtor, NULL);
+  jint flags = location->flags;
+
+  // set the valid information in the object
+  if (flags & FLP_LOCATION_HAS_LAT_LONG) {
+    jmethodID setLatitude = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setLatitude",
+        "(D)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setLatitude, location->latitude);
+
+    jmethodID setLongitude = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setLongitude",
+        "(D)V"
+        );
+    sCallbackEnv->CallVoidMethod(
+        locationObject,
+        setLongitude,
+        location->longitude
+        );
+
+    jmethodID setTime = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setTime",
+        "(J)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setTime, location->timestamp);
+  }
+
+  if (flags & FLP_LOCATION_HAS_ALTITUDE) {
+    jmethodID setAltitude = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setAltitude",
+        "(D)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setAltitude, location->altitude);
+  }
+
+  if (flags & FLP_LOCATION_HAS_SPEED) {
+    jmethodID setSpeed = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setSpeed",
+        "(F)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setSpeed, location->speed);
+  }
+
+  if (flags & FLP_LOCATION_HAS_BEARING) {
+    jmethodID setBearing = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setBearing",
+        "(F)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setBearing, location->bearing);
+  }
+
+  if (flags & FLP_LOCATION_HAS_ACCURACY) {
+    jmethodID setAccuracy = sCallbackEnv->GetMethodID(
+        locationClass,
+        "setAccuracy",
+        "(F)V"
+        );
+    sCallbackEnv->CallVoidMethod(locationObject, setAccuracy, location->accuracy);
+  }
+
+  // TODO: wire FlpLocation::sources_used when needed
+}
+
+/*
+ * Helper function to serialize FlpLocation structures.
+ */
+static void TranslateToObjectArray(
+    int32_t locationsCount,
+    FlpLocation** locations,
+    jobjectArray& locationsArray) {
+  jclass locationClass = sCallbackEnv->FindClass(LOCATION_CLASS_NAME);
+  locationsArray = sCallbackEnv->NewObjectArray(
+      locationsCount,
+      locationClass,
+      /* initialElement */ NULL
+      );
+
+  for (int i = 0; i < locationsCount; ++i) {
+    jobject locationObject = NULL;
+    TranslateToObject(locations[i], locationObject);
+    sCallbackEnv->SetObjectArrayElement(locationsArray, i, locationObject);
+    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__);
+}
+
+static void AcquireWakelock() {
+  acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
+}
+
+static void ReleaseWakelock() {
+  release_wake_lock(WAKE_LOCK_NAME);
+}
+
+FlpCallbacks sFlpCallbacks = {
+  sizeof(FlpCallbacks),
+  LocationCallback,
+  AcquireWakelock,
+  ReleaseWakelock,
+  SetThreadEvent
+};
+
+static void ReportData(char* data, int length) {
+  jstring stringData = NULL;
+
+  if(length != 0 && data != NULL) {
+    stringData = sCallbackEnv->NewString(reinterpret_cast<jchar*>(data), length);
+  } else {
+    ALOGE("Invalid ReportData callback. Length: %d, Data: %p", length, data);
+    return;
+  }
+
+  sCallbackEnv->CallVoidMethod(sCallbacksObj, sOnDataReport, stringData);
+  CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+FlpDiagnosticCallbacks sFlpDiagnosticCallbacks = {
+  sizeof(FlpDiagnosticCallbacks),
+  SetThreadEvent,
+  ReportData
+};
+
+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__);
+}
+
+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__);
+}
+
+static void GeofenceAddCallback(int32_t geofenceId, int32_t result) {
+  if(!IsValidCallbackThread()) {
+    return;
+  }
+
+  sCallbackEnv->CallVoidMethod(sCallbacksObj, sOnGeofenceAdd, geofenceId, result);
+  CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+static void GeofenceRemoveCallback(int32_t geofenceId, int32_t result) {
+  if(!IsValidCallbackThread()) {
+    return;
+  }
+
+  sCallbackEnv->CallVoidMethod(
+      sCallbacksObj,
+      sOnGeofenceRemove,
+      geofenceId,
+      result
+      );
+  CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+static void GeofencePauseCallback(int32_t geofenceId, int32_t result) {
+  if(!IsValidCallbackThread()) {
+    return;
+  }
+
+  sCallbackEnv->CallVoidMethod(
+      sCallbacksObj,
+      sOnGeofencePause,
+      geofenceId,
+      result
+      );
+  CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+static void GeofenceResumeCallback(int32_t geofenceId, int32_t result) {
+  if(!IsValidCallbackThread()) {
+    return;
+  }
+
+  sCallbackEnv->CallVoidMethod(
+      sCallbacksObj,
+      sOnGeofenceResume,
+      geofenceId,
+      result
+      );
+  CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+FlpGeofenceCallbacks sFlpGeofenceCallbacks = {
+  sizeof(FlpGeofenceCallbacks),
+  GeofenceTransitionCallback,
+  GeofenceMonitorStatusCallback,
+  GeofenceAddCallback,
+  GeofenceRemoveCallback,
+  GeofencePauseCallback,
+  GeofenceResumeCallback,
+  SetThreadEvent
+};
+
+/*
+ * Initializes the Fused Location Provider in the native side. It ensures that
+ * the Flp interfaces are initialized properly.
+ */
+static void Init(JNIEnv* env, jobject obj) {
+  if(sHardwareDevice != NULL) {
+    ALOGD("Hardware Device already opened.");
+    return;
+  }
+
+  const hw_module_t* module = NULL;
+  int err = hw_get_module(FUSED_LOCATION_HARDWARE_MODULE_ID, &module);
+  if(err != 0) {
+    ALOGE("Error hw_get_module '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
+    return;
+  }
+
+  err = module->methods->open(
+        module, 
+        FUSED_LOCATION_HARDWARE_MODULE_ID, &sHardwareDevice);
+  if(err != 0) {
+    ALOGE("Error opening device '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
+    return;
+  }
+
+  sFlpInterface = NULL;
+  flp_device_t* flp_device = reinterpret_cast<flp_device_t*>(sHardwareDevice);
+  sFlpInterface = flp_device->get_flp_interface(flp_device);
+
+  if(sFlpInterface != NULL) {
+    sFlpDiagnosticInterface = reinterpret_cast<const FlpDiagnosticInterface*>(
+        sFlpInterface->get_extension(FLP_DIAGNOSTIC_INTERFACE)
+        );
+
+    sFlpGeofencingInterface = reinterpret_cast<const FlpGeofencingInterface*>(
+        sFlpInterface->get_extension(FLP_GEOFENCING_INTERFACE)
+        );
+
+    sFlpDeviceContextInterface = reinterpret_cast<const FlpDeviceContextInterface*>(
+        sFlpInterface->get_extension(FLP_DEVICE_CONTEXT_INTERFACE)
+        );
+  }
+
+  if(sCallbacksObj == NULL) {
+    sCallbacksObj = env->NewGlobalRef(obj);
+  }
+
+  // initialize the Flp interfaces
+  if(sFlpInterface == NULL || sFlpInterface->init(&sFlpCallbacks) != 0) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  if(sFlpDiagnosticInterface != NULL) {
+    sFlpDiagnosticInterface->init(&sFlpDiagnosticCallbacks);
+  }
+
+  if(sFlpGeofencingInterface != NULL) {
+    sFlpGeofencingInterface->init(&sFlpGeofenceCallbacks);
+  }
+
+  // TODO: inject any device context if when needed
+}
+
+static jboolean IsSupported(JNIEnv* env, jclass clazz) {
+  return sFlpInterface != NULL;
+}
+
+static jint GetBatchSize(JNIEnv* env, jobject object) {
+  if(sFlpInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  return sFlpInterface->get_batch_size();
+}
+
+static void StartBatching(
+    JNIEnv* env,
+    jobject object,
+    jint id,
+    jobject optionsObject) {
+  if(sFlpInterface == NULL || optionsObject == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  FlpBatchOptions options;
+  TranslateFromObject(env, optionsObject, options);
+  int result = sFlpInterface->start_batching(id, &options);
+  ThrowOnError(env, result, __FUNCTION__);
+}
+
+static void UpdateBatchingOptions(
+    JNIEnv* env,
+    jobject object,
+    jint id,
+    jobject optionsObject) {
+  if(sFlpInterface == NULL || optionsObject == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  FlpBatchOptions options;
+  TranslateFromObject(env, optionsObject, options);
+  int result = sFlpInterface->update_batching_options(id, &options);
+  ThrowOnError(env, result, __FUNCTION__);
+}
+
+static void StopBatching(JNIEnv* env, jobject object, jint id) {
+  if(sFlpInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpInterface->stop_batching(id);
+}
+
+static void Cleanup(JNIEnv* env, jobject object) {
+  if(sFlpInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpInterface->cleanup();
+
+  if(sCallbacksObj != NULL) {
+    env->DeleteGlobalRef(sCallbacksObj);
+    sCallbacksObj = NULL;
+  }
+
+  sFlpInterface = NULL;
+  sFlpDiagnosticInterface = NULL;
+  sFlpDeviceContextInterface = NULL;
+  sFlpGeofencingInterface = NULL;
+
+  if(sHardwareDevice != NULL) {
+    sHardwareDevice->close(sHardwareDevice);
+    sHardwareDevice = NULL;
+  }
+}
+
+static void GetBatchedLocation(JNIEnv* env, jobject object, jint lastNLocations) {
+  if(sFlpInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpInterface->get_batched_location(lastNLocations);
+}
+
+static void InjectLocation(JNIEnv* env, jobject object, jobject locationObject) {
+  if(locationObject == NULL) {
+    ALOGE("Invalid location for injection: %p", locationObject);
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  if(sFlpInterface == NULL) {
+    // there is no listener, bail
+    return;
+  }
+
+  FlpLocation location;
+  TranslateFromObject(env, locationObject, location);
+  int result = sFlpInterface->inject_location(&location);
+  if (result != FLP_RESULT_ERROR) {
+    // do not throw but log, this operation should be fire and forget
+    ALOGE("Error %d in '%s'", result, __FUNCTION__);
+  }
+}
+
+static jboolean IsDiagnosticSupported() {
+  return sFlpDiagnosticInterface != NULL;
+}
+
+static void InjectDiagnosticData(JNIEnv* env, jobject object, jstring stringData) {
+  if(stringData == NULL) {
+    ALOGE("Invalid diagnostic data for injection: %p", stringData);
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  if(sFlpDiagnosticInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  int length = env->GetStringLength(stringData);
+  const jchar* data = env->GetStringChars(stringData, /* isCopy */ NULL);
+  if(data == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  int result = sFlpDiagnosticInterface->inject_data((char*) data, length);
+  ThrowOnError(env, result, __FUNCTION__);
+}
+
+static jboolean IsDeviceContextSupported() {
+  return sFlpDeviceContextInterface != NULL;
+}
+
+static void InjectDeviceContext(JNIEnv* env, jobject object, jint enabledMask) {
+  if(sFlpDeviceContextInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  int result = sFlpDeviceContextInterface->inject_device_context(enabledMask);
+  ThrowOnError(env, result, __FUNCTION__);
+}
+
+static jboolean IsGeofencingSupported() {
+  return sFlpGeofencingInterface != NULL;
+}
+
+static void AddGeofences(
+    JNIEnv* env,
+    jobject object,
+    jintArray geofenceIdsArray,
+    jobjectArray geofencesArray) {
+  if(geofencesArray == NULL) {
+    ALOGE("Invalid Geofences to add: %p", geofencesArray);
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  if (sFlpGeofencingInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  jint geofencesCount = env->GetArrayLength(geofenceIdsArray);
+  Geofence* geofences = new Geofence[geofencesCount];
+  if (geofences == NULL) {
+    ThrowOnError(env, FLP_RESULT_INSUFFICIENT_MEMORY, __FUNCTION__);
+  }
+
+  jint* ids = env->GetIntArrayElements(geofenceIdsArray, /* isCopy */ NULL);
+  for (int i = 0; i < geofencesCount; ++i) {
+    geofences[i].geofence_id = ids[i];
+
+    // TODO: fill in the GeofenceData
+
+    // TODO: fill in the GeofenceOptions
+  }
+
+  sFlpGeofencingInterface->add_geofences(geofencesCount, &geofences);
+  if (geofences != NULL) delete[] geofences;
+}
+
+static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) {
+  if(sFlpGeofencingInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpGeofencingInterface->pause_geofence(geofenceId);
+}
+
+static void ResumeGeofence(
+    JNIEnv* env,
+    jobject object,
+    jint geofenceId,
+    jint monitorTransitions) {
+  if(sFlpGeofencingInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpGeofencingInterface->resume_geofence(geofenceId, monitorTransitions);
+}
+
+static void ModifyGeofenceOption(
+    JNIEnv* env,
+    jobject object,
+    jint geofenceId,
+    jint lastTransition,
+    jint monitorTransitions,
+    jint notificationResponsiveness,
+    jint unknownTimer,
+    jint sourcesToUse) {
+  if(sFlpGeofencingInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  GeofenceOptions options = {
+      lastTransition,
+      monitorTransitions,
+      notificationResponsiveness,
+      unknownTimer,
+      (uint32_t)sourcesToUse
+  };
+
+  sFlpGeofencingInterface->modify_geofence_option(geofenceId, &options);
+}
+
+static void RemoveGeofences(
+    JNIEnv* env,
+    jobject object,
+    jintArray geofenceIdsArray) {
+  if(sFlpGeofencingInterface == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  jsize geofenceIdsCount = env->GetArrayLength(geofenceIdsArray);
+  jint* geofenceIds = env->GetIntArrayElements(geofenceIdsArray, /* isCopy */ NULL);
+  if(geofenceIds == NULL) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+
+  sFlpGeofencingInterface->remove_geofences(geofenceIdsCount, geofenceIds);
+}
+
+static JNINativeMethod sMethods[] = {
+  //{"name", "signature", functionPointer }
+  {"nativeClassInit", "()V", reinterpret_cast<void*>(ClassInit)},
+  {"nativeInit", "()V", reinterpret_cast<void*>(Init)},
+  {"nativeCleanup", "()V", reinterpret_cast<void*>(Cleanup)},
+  {"nativeIsSupported", "()Z", reinterpret_cast<void*>(IsSupported)},
+  {"nativeGetBatchSize", "()I", reinterpret_cast<void*>(GetBatchSize)},
+  {"nativeStartBatching", 
+        "(ILandroid/location/FusedBatchOptions;)V", 
+        reinterpret_cast<void*>(StartBatching)},
+  {"nativeUpdateBatchingOptions", 
+        "(ILandroid/location/FusedBatchOptions;)V", 
+        reinterpret_cast<void*>(UpdateBatchingOptions)},
+  {"nativeStopBatching", "(I)V", reinterpret_cast<void*>(StopBatching)},
+  {"nativeRequestBatchedLocation", 
+        "(I)V", 
+        reinterpret_cast<void*>(GetBatchedLocation)},
+  {"nativeInjectLocation", 
+        "(Landroid/location/Location;)V", 
+        reinterpret_cast<void*>(InjectLocation)},
+  {"nativeIsDiagnosticSupported", 
+        "()Z", 
+        reinterpret_cast<void*>(IsDiagnosticSupported)},
+  {"nativeInjectDiagnosticData", 
+        "(Ljava/lang/String;)V", 
+        reinterpret_cast<void*>(InjectDiagnosticData)},
+  {"nativeIsDeviceContextSupported", 
+        "()Z", 
+        reinterpret_cast<void*>(IsDeviceContextSupported)},
+  {"nativeInjectDeviceContext", 
+        "(I)V", 
+        reinterpret_cast<void*>(InjectDeviceContext)},
+  {"nativeIsGeofencingSupported", 
+        "()Z", 
+        reinterpret_cast<void*>(IsGeofencingSupported)},
+  {"nativeAddGeofences", 
+        "([I[Landroid/location/Geofence;)V", 
+        reinterpret_cast<void*>(AddGeofences)},
+  {"nativePauseGeofence", "(I)V", reinterpret_cast<void*>(PauseGeofence)},
+  {"nativeResumeGeofence", "(II)V", reinterpret_cast<void*>(ResumeGeofence)},
+  {"nativeModifyGeofenceOption", 
+        "(IIIIII)V", 
+        reinterpret_cast<void*>(ModifyGeofenceOption)},
+  {"nativeRemoveGeofences", "([I)V", reinterpret_cast<void*>(RemoveGeofences)}
+};
+
+/*
+ * Registration method invoked on JNI Load.
+ */
+int register_android_server_location_FlpHardwareProvider(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env,
+      "com/android/server/location/FlpHardwareProvider",
+      sMethods,
+      NELEM(sMethods)
+      );
+}
+
+} /* name-space Android */