jni: Extending native interface to NFD

- `startNfd` now accepts Map<String, String> instead of just a single
  String/homeDir parameter.
- new `getNfdLogModules` method returns all available log modules

Change-Id: I597579c69be06607de0734c034226fe9a359a2bc
Refs: #2746, #2623
diff --git a/app/src/main/java/net/named_data/nfd/MainFragment.java b/app/src/main/java/net/named_data/nfd/MainFragment.java
index 68e9690..96443c0 100644
--- a/app/src/main/java/net/named_data/nfd/MainFragment.java
+++ b/app/src/main/java/net/named_data/nfd/MainFragment.java
@@ -78,8 +78,7 @@
       @Override
       public void onCheckedChanged(CompoundButton compoundButton, boolean isOn)
       {
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
-        sp.edit()
+        m_sharedPreferences.edit()
           .putBoolean(PREF_NFD_SERVICE_STATUS, isOn)
           .apply();
 
@@ -110,6 +109,13 @@
   }
 
   @Override
+  public void onActivityCreated(@Nullable Bundle savedInstanceState)
+  {
+    super.onActivityCreated(savedInstanceState);
+    m_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
+  }
+
+  @Override
   public void
   onResume() {
     super.onResume();
@@ -232,7 +238,7 @@
           setNfdServiceRunning();
           G.Log("ClientHandler: NFD is Running.");
 
-          m_handler.post(m_statusUpdateRunnable);
+          m_handler.postDelayed(m_statusUpdateRunnable, 500);
           break;
 
         case NfdService.NFD_SERVICE_STOPPED:
@@ -260,8 +266,7 @@
 
       // Check if NFD Service is running
       try {
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
-        boolean shouldServiceBeOn = sp.getBoolean(PREF_NFD_SERVICE_STATUS, true);
+        boolean shouldServiceBeOn = m_sharedPreferences.getBoolean(PREF_NFD_SERVICE_STATUS, true);
 
         Message msg = Message.obtain(null, shouldServiceBeOn ? NfdService.START_NFD_SERVICE : NfdService.STOP_NFD_SERVICE);
         msg.replyTo = m_clientMessenger;
@@ -330,7 +335,8 @@
     onPostExecute(ForwarderStatus fs)
     {
       if (fs == null) {
-        // Maybe display error message from result.second
+        // when failed, try after 0.5 seconds
+        m_handler.postDelayed(m_statusUpdateRunnable, 500);
       }
       else {
         m_versionView.setText(fs.getNfdVersion());
@@ -351,9 +357,10 @@
         m_outDataView.setText(String.valueOf(fs.getNumOutDatas()));
 
         m_nfdStatusView.setVisibility(View.VISIBLE);
-      }
 
-      m_handler.postDelayed(m_statusUpdateRunnable, 5000);
+        // refresh after 5 seconds
+        m_handler.postDelayed(m_statusUpdateRunnable, 5000);
+      }
     }
   }
 
@@ -395,5 +402,7 @@
     }
   };
 
+  private SharedPreferences m_sharedPreferences;
+
   private static final String PREF_NFD_SERVICE_STATUS = "NFD_SERVICE_STATUS";
 }
diff --git a/app/src/main/java/net/named_data/nfd/service/NfdService.java b/app/src/main/java/net/named_data/nfd/service/NfdService.java
index e4226db..1b43df3 100644
--- a/app/src/main/java/net/named_data/nfd/service/NfdService.java
+++ b/app/src/main/java/net/named_data/nfd/service/NfdService.java
@@ -29,6 +29,12 @@
 
 import net.named_data.nfd.utils.G;
 
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * NfdService that runs the native NFD.
  *
@@ -68,11 +74,11 @@
   /**
    * Native API for starting the NFD.
    *
-   * @param homePath Absolute path of the home directory for the service;
-   *                 Usually achieved by calling ContextWrapper.getFilesDir().getAbsolutePath()
+   * @param params NFD parameters.  Must include 'homePath' with absolute path of the home directory
+   *               for the service (ContextWrapper.getFilesDir().getAbsolutePath())
    */
   public native static void
-  startNfd(String homePath);
+  startNfd(Map<String, String> params);
 
   /**
    * Native API for stopping the NFD.
@@ -80,6 +86,9 @@
   public native static void
   stopNfd();
 
+  public native static List<String>
+  getNfdLogModules();
+
   /** Message to start NFD Service */
   public static final int START_NFD_SERVICE = 1;
 
@@ -144,7 +153,17 @@
   serviceStartNfd() {
     if (!m_isNfdStarted) {
       m_isNfdStarted = true;
-      startNfd(getFilesDir().getAbsolutePath());
+      HashMap<String, String> params = new HashMap<>();
+      params.put("homePath", getFilesDir().getAbsolutePath());
+      Set<Map.Entry<String,String>> e = params.entrySet();
+
+      startNfd(params);
+
+      // Example how to retrieve all available NFD log modules
+      List<String> modules = getNfdLogModules();
+      for (String module : modules) {
+        G.Log(module);
+      }
 
       // TODO: Reload NFD and NRD in memory structures (if any)
 
diff --git a/app/src/main/jni/nfd-wrapper.cpp b/app/src/main/jni/nfd-wrapper.cpp
index d16049d..a5544a8 100644
--- a/app/src/main/jni/nfd-wrapper.cpp
+++ b/app/src/main/jni/nfd-wrapper.cpp
@@ -192,18 +192,61 @@
 
 static unique_ptr<Runner> g_runner;
 static boost::thread g_thread;
+static std::map<std::string, std::string> g_params;
 
 } // namespace nfd
 
+
+std::map<std::string, std::string>
+getParams(JNIEnv* env, jobject jParams)
+{
+  std::map<std::string, std::string> params;
+
+  jclass jcMap = env->GetObjectClass(jParams);
+  jclass jcSet = env->FindClass("java/util/Set");
+  jclass jcIterator = env->FindClass("java/util/Iterator");
+  jclass jcMapEntry = env->FindClass("java/util/Map$Entry");
+
+  jmethodID jcMapEntrySet      = env->GetMethodID(jcMap,      "entrySet", "()Ljava/util/Set;");
+  jmethodID jcSetIterator      = env->GetMethodID(jcSet,      "iterator", "()Ljava/util/Iterator;");
+  jmethodID jcIteratorHasNext  = env->GetMethodID(jcIterator, "hasNext",  "()Z");
+  jmethodID jcIteratorNext     = env->GetMethodID(jcIterator, "next",     "()Ljava/lang/Object;");
+  jmethodID jcMapEntryGetKey   = env->GetMethodID(jcMapEntry, "getKey",   "()Ljava/lang/Object;");
+  jmethodID jcMapEntryGetValue = env->GetMethodID(jcMapEntry, "getValue", "()Ljava/lang/Object;");
+
+  jobject jParamsEntrySet = env->CallObjectMethod(jParams, jcMapEntrySet);
+  jobject jParamsIterator = env->CallObjectMethod(jParamsEntrySet, jcSetIterator);
+  jboolean bHasNext = env->CallBooleanMethod(jParamsIterator, jcIteratorHasNext);
+  while (bHasNext) {
+    jobject entry = env->CallObjectMethod(jParamsIterator, jcIteratorNext);
+
+    jstring jKey = (jstring)env->CallObjectMethod(entry, jcMapEntryGetKey);
+    jstring jValue = (jstring)env->CallObjectMethod(entry, jcMapEntryGetValue);
+
+    const char* cKey = env->GetStringUTFChars(jKey, nullptr);
+    const char* cValue = env->GetStringUTFChars(jValue, nullptr);
+
+    params.insert(std::make_pair(cKey, cValue));
+
+    env->ReleaseStringUTFChars(jKey, cKey);
+    env->ReleaseStringUTFChars(jValue, cValue);
+
+    bHasNext = env->CallBooleanMethod(jParamsIterator, jcIteratorHasNext);
+  }
+
+  return params;
+}
+
+
 JNIEXPORT void JNICALL
-Java_net_named_1data_nfd_service_NfdService_startNfd(JNIEnv* env, jclass, jstring homePathJ)
+Java_net_named_1data_nfd_service_NfdService_startNfd(JNIEnv* env, jclass, jobject jParams)
 {
   if (nfd::g_runner.get() == nullptr) {
+    nfd::g_params = getParams(env, jParams);
+
     // set/update HOME environment variable
-    const char* homePath = env->GetStringUTFChars(homePathJ, nullptr);
-    ::setenv("HOME", homePath, true);
-    env->ReleaseStringUTFChars(homePathJ, homePath);
-    NFD_LOG_INFO("Use [" << homePath << "] as a security storage");
+    ::setenv("HOME", nfd::g_params["homePath"].c_str(), true);
+    NFD_LOG_INFO("Use [" << nfd::g_params["homePath"] << "] as a security storage");
 
     nfd::g_thread = boost::thread([] {
         NFD_LOG_INFO("Starting NFD...");
@@ -238,3 +281,20 @@
     // do not block anything
   }
 }
+
+JNIEXPORT jobject JNICALL
+Java_net_named_1data_nfd_service_NfdService_getNfdLogModules(JNIEnv* env, jclass)
+{
+  jclass jcLinkedList = env->FindClass("java/util/LinkedList");
+  jmethodID jcLinkedListConstructor = env->GetMethodID(jcLinkedList, "<init>", "()V");
+  jmethodID jcLinkedListAdd = env->GetMethodID(jcLinkedList, "add", "(Ljava/lang/Object;)Z");
+
+  jobject jModules = env->NewObject(jcLinkedList, jcLinkedListConstructor);
+
+  for (const auto& module : nfd::LoggerFactory::getInstance().getModules()) {
+    jstring jModule = env->NewStringUTF(module.c_str());
+    env->CallBooleanMethod(jModules, jcLinkedListAdd, jModule);
+  }
+
+  return jModules;
+}
diff --git a/app/src/main/jni/nfd-wrapper.hpp b/app/src/main/jni/nfd-wrapper.hpp
index 97e3cc1..fcd598d 100644
--- a/app/src/main/jni/nfd-wrapper.hpp
+++ b/app/src/main/jni/nfd-wrapper.hpp
@@ -26,13 +26,14 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
+
 /*
  * Class:     net_named_data_nfd_service_NfdService
  * Method:    startNfd
- * Signature: (Ljava/lang/String;)V
+ * Signature: (Ljava/lang/Map;)V
  */
 JNIEXPORT void JNICALL
-Java_net_named_1data_nfd_service_NfdService_startNfd(JNIEnv*, jclass, jstring);
+Java_net_named_1data_nfd_service_NfdService_startNfd(JNIEnv*, jclass, jobject);
 
 /*
  * Class:     net_named_data_nfd_service_NfdService
@@ -42,6 +43,14 @@
 JNIEXPORT void JNICALL
 Java_net_named_1data_nfd_service_NfdService_stopNfd(JNIEnv*, jclass);
 
+/*
+ * Class:     net_named_data_nfd_service_NfdService
+ * Method:    getNfdLogModules
+ * Signature: ()Ljava/util/List;
+ */
+JNIEXPORT jobject JNICALL
+Java_net_named_1data_nfd_service_NfdService_getNfdLogModules(JNIEnv*, jclass);
+
 #ifdef __cplusplus
 }
 #endif