Implements JavaToNativeStringMap and adds tests for native API.

Implements JavaToNativeStringMap that is a replacement for
JavaToStdMapStrings. It uses a new template method JavaToNativeMap. Also
adds testing support for native API and a test for JavaToNativeStringMap.

Bug: webrtc:8769
Change-Id: I580d4992a899ebe02da39af450fa51d52ee9b88b
Reviewed-on: https://webrtc-review.googlesource.com/48060
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21967}
diff --git a/BUILD.gn b/BUILD.gn
index 45069a4..10be0ca 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -446,7 +446,14 @@
     }
 
     if (is_android) {
-      deps += [ "//testing/android/native_test:native_test_support" ]
+      # Do not use Chromium's launcher. native_unittests defines its own JNI_OnLoad.
+      use_default_launcher = false
+
+      deps += [
+        "sdk/android:native_unittests",
+        "sdk/android:native_unittests_java",
+        "//testing/android/native_test:native_test_support",
+      ]
       shard_timeout = 900
     }
 
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index 7a6c036..9225c6f 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -892,3 +892,41 @@
     "//rtc_base:rtc_base_approved",
   ]
 }
+
+generate_jni("generated_native_unittests_jni") {
+  testonly = true
+
+  sources = [
+    "native_unittests/org/webrtc/JavaTypesTestHelper.java",
+  ]
+  jni_package = ""
+  jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"
+}
+
+rtc_android_library("native_unittests_java") {
+  testonly = true
+
+  java_files = [ "native_unittests/org/webrtc/JavaTypesTestHelper.java" ]
+
+  deps = [
+    ":libjingle_peerconnection_java",
+  ]
+}
+
+rtc_source_set("native_unittests") {
+  testonly = true
+
+  sources = [
+    "native_unittests/java_types_unittest.cc",
+    "native_unittests/test_jni_onload.cc",
+  ]
+
+  deps = [
+    ":generated_native_unittests_jni",
+    ":native_api_base",
+    ":native_api_jni",
+    "//rtc_base:checks",
+    "//test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/sdk/android/native_api/jni/java_types.cc b/sdk/android/native_api/jni/java_types.cc
index 156925c..86ada91 100644
--- a/sdk/android/native_api/jni/java_types.cc
+++ b/sdk/android/native_api/jni/java_types.cc
@@ -30,6 +30,8 @@
 Iterable::Iterable(JNIEnv* jni, const JavaRef<jobject>& iterable)
     : jni_(jni), iterable_(jni, iterable) {}
 
+Iterable::Iterable(Iterable&& other) = default;
+
 Iterable::~Iterable() = default;
 
 // Creates an iterator representing the end of any collection.
@@ -103,6 +105,22 @@
   return JavaToStdString(jni, JNI_Enum::Java_Enum_name(jni, j_enum));
 }
 
+Iterable GetJavaMapEntrySet(JNIEnv* jni, const JavaRef<jobject>& j_map) {
+  return Iterable(jni, JNI_Map::Java_Map_entrySet(jni, j_map));
+}
+
+ScopedJavaLocalRef<jobject> GetJavaMapEntryKey(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_entry) {
+  return Java_JniHelper_getKey(jni, j_entry);
+}
+
+ScopedJavaLocalRef<jobject> GetJavaMapEntryValue(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_entry) {
+  return Java_JniHelper_getValue(jni, j_entry);
+}
+
 int64_t JavaToNativeLong(JNIEnv* env, const JavaRef<jobject>& j_long) {
   return JNI_Long::Java_Long_longValue(env, j_long);
 }
@@ -136,6 +154,19 @@
   return str;
 }
 
+std::map<std::string, std::string> JavaToNativeStringMap(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_map) {
+  return JavaToNativeMap<std::string, std::string>(
+      jni, j_map,
+      [](JNIEnv* env, JavaRef<jobject> const& key,
+         JavaRef<jobject> const& value) {
+        return std::make_pair(
+            JavaToNativeString(env, static_java_ref_cast<jstring>(env, key)),
+            JavaToNativeString(env, static_java_ref_cast<jstring>(env, value)));
+      });
+}
+
 ScopedJavaLocalRef<jobject> NativeToJavaBoolean(JNIEnv* env, bool b) {
   return JNI_Boolean::Java_Boolean_ConstructorJLB_Z(env, b);
 }
@@ -242,18 +273,4 @@
   return converted_list;
 }
 
-std::map<std::string, std::string> JavaToStdMapStrings(
-    JNIEnv* jni,
-    const JavaRef<jobject>& j_map) {
-  const JavaRef<jobject>& j_entry_set = JNI_Map::Java_Map_entrySet(jni, j_map);
-  std::map<std::string, std::string> result;
-  for (const JavaRef<jobject>& j_entry : Iterable(jni, j_entry_set)) {
-    result.insert(std::make_pair(
-        JavaToStdString(jni, Java_JniHelper_getKey(jni, j_entry)),
-        JavaToStdString(jni, Java_JniHelper_getValue(jni, j_entry))));
-  }
-
-  return result;
-}
-
 }  // namespace webrtc
diff --git a/sdk/android/native_api/jni/java_types.h b/sdk/android/native_api/jni/java_types.h
index 2f5e8f8..d9d2b1a 100644
--- a/sdk/android/native_api/jni/java_types.h
+++ b/sdk/android/native_api/jni/java_types.h
@@ -50,6 +50,8 @@
 class Iterable {
  public:
   Iterable(JNIEnv* jni, const JavaRef<jobject>& iterable);
+  Iterable(Iterable&& other);
+
   ~Iterable();
 
   class Iterator {
@@ -111,6 +113,13 @@
 // Returns the name of a Java enum.
 std::string GetJavaEnumName(JNIEnv* jni, const JavaRef<jobject>& j_enum);
 
+Iterable GetJavaMapEntrySet(JNIEnv* jni, const JavaRef<jobject>& j_map);
+ScopedJavaLocalRef<jobject> GetJavaMapEntryKey(JNIEnv* jni,
+                                               const JavaRef<jobject>& j_entry);
+ScopedJavaLocalRef<jobject> GetJavaMapEntryValue(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_entry);
+
 // --------------------------------------------------------
 // -- Methods for converting Java types to native types. --
 // --------------------------------------------------------
@@ -141,6 +150,23 @@
   return container;
 }
 
+template <typename Key, typename T, typename Convert>
+std::map<Key, T> JavaToNativeMap(JNIEnv* env,
+                                 const JavaRef<jobject>& j_map,
+                                 Convert convert) {
+  std::map<Key, T> container;
+  for (auto const& j_entry : GetJavaMapEntrySet(env, j_map)) {
+    container.emplace(convert(env, GetJavaMapEntryKey(env, j_entry),
+                              GetJavaMapEntryValue(env, j_entry)));
+  }
+  return container;
+}
+
+// Converts Map<String, String> to std::map<std::string, std::string>.
+std::map<std::string, std::string> JavaToNativeStringMap(
+    JNIEnv* env,
+    const JavaRef<jobject>& j_map);
+
 // --------------------------------------------------------
 // -- Methods for converting native types to Java types. --
 // --------------------------------------------------------
@@ -262,10 +288,13 @@
 std::vector<std::string> JavaToStdVectorStrings(JNIEnv* jni,
                                                 const JavaRef<jobject>& list);
 
+// Deprecated. Use JavaToNativeStringMap instead.
 // Parses Map<String, String> to std::map<std::string, std::string>.
-std::map<std::string, std::string> JavaToStdMapStrings(
+inline std::map<std::string, std::string> JavaToStdMapStrings(
     JNIEnv* jni,
-    const JavaRef<jobject>& j_map);
+    const JavaRef<jobject>& j_map) {
+  return JavaToNativeStringMap(jni, j_map);
+}
 
 // Deprecated. Use scoped jobjects instead.
 inline std::map<std::string, std::string> JavaToStdMapStrings(JNIEnv* jni,
diff --git a/sdk/android/native_api/jni/scoped_java_ref.h b/sdk/android/native_api/jni/scoped_java_ref.h
index e3259f1..02948e0 100644
--- a/sdk/android/native_api/jni/scoped_java_ref.h
+++ b/sdk/android/native_api/jni/scoped_java_ref.h
@@ -197,6 +197,13 @@
   RTC_DISALLOW_COPY_AND_ASSIGN(ScopedJavaGlobalRef);
 };
 
+template <typename T>
+inline ScopedJavaLocalRef<T> static_java_ref_cast(JNIEnv* env,
+                                                  JavaRef<jobject> const& ref) {
+  ScopedJavaLocalRef<jobject> owned_ref(env, ref);
+  return ScopedJavaLocalRef<T>(env, static_cast<T>(owned_ref.Release()));
+}
+
 }  // namespace webrtc
 
 #endif  // SDK_ANDROID_NATIVE_API_JNI_SCOPED_JAVA_REF_H_
diff --git a/sdk/android/native_unittests/java_types_unittest.cc b/sdk/android/native_unittests/java_types_unittest.cc
new file mode 100644
index 0000000..a549e03
--- /dev/null
+++ b/sdk/android/native_unittests/java_types_unittest.cc
@@ -0,0 +1,35 @@
+/*
+ *  Copyright (c) 2018 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.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "sdk/android/generated_native_unittests_jni/jni/JavaTypesTestHelper_jni.h"
+#include "sdk/android/native_api/jni/java_types.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+TEST(JavaTypesTest, TestJavaToNativeStringMap) {
+  JNIEnv* env = AttachCurrentThreadIfNeeded();
+  ScopedJavaLocalRef<jobject> j_map =
+      Java_JavaTypesTestHelper_createTestStringMap(env);
+
+  std::map<std::string, std::string> output = JavaToNativeStringMap(env, j_map);
+
+  std::map<std::string, std::string> expected{
+      {"one", "1"}, {"two", "2"}, {"three", "3"},
+  };
+  EXPECT_EQ(expected, output);
+}
+}  // namespace
+}  // namespace test
+}  // namespace webrtc
diff --git a/sdk/android/native_unittests/org/webrtc/JavaTypesTestHelper.java b/sdk/android/native_unittests/org/webrtc/JavaTypesTestHelper.java
new file mode 100644
index 0000000..6695ef7
--- /dev/null
+++ b/sdk/android/native_unittests/org/webrtc/JavaTypesTestHelper.java
@@ -0,0 +1,25 @@
+/*
+ *  Copyright (c) 2018 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.
+ */
+
+package org.webrtc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JavaTypesTestHelper {
+  @CalledByNative
+  public static Map createTestStringMap() {
+    Map<String, String> testMap = new HashMap<String, String>();
+    testMap.put("one", "1");
+    testMap.put("two", "2");
+    testMap.put("three", "3");
+    return testMap;
+  }
+}
diff --git a/sdk/android/native_unittests/test_jni_onload.cc b/sdk/android/native_unittests/test_jni_onload.cc
new file mode 100644
index 0000000..dafe49c
--- /dev/null
+++ b/sdk/android/native_unittests/test_jni_onload.cc
@@ -0,0 +1,23 @@
+/*
+ *  Copyright (c) 2018 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.
+ */
+
+#include <jni.h>
+#undef JNIEXPORT
+#define JNIEXPORT __attribute__((visibility("default")))
+
+#include "rtc_base/checks.h"
+#include "sdk/android/native_api/base/init.h"
+#include "sdk/android/native_api/jni/java_types.h"
+
+// This is called by the VM when the shared library is first loaded.
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  webrtc::InitAndroid(vm);
+  return JNI_VERSION_1_4;
+}
diff --git a/sdk/android/src/java/org/webrtc/JniHelper.java b/sdk/android/src/java/org/webrtc/JniHelper.java
index 5b00ffe..0d56d5d 100644
--- a/sdk/android/src/java/org/webrtc/JniHelper.java
+++ b/sdk/android/src/java/org/webrtc/JniHelper.java
@@ -36,13 +36,13 @@
 
   // TODO(bugs.webrtc.org/8606): Remove.
   @CalledByNative
-  static String getKey(Map.Entry<String, String> entry) {
+  static Object getKey(Map.Entry entry) {
     return entry.getKey();
   }
 
   // TODO(bugs.webrtc.org/8606): Remove.
   @CalledByNative
-  static String getValue(Map.Entry<String, String> entry) {
+  static Object getValue(Map.Entry entry) {
     return entry.getValue();
   }
 }
diff --git a/sdk/android/src/jni/hardwarevideoencoderfactory.cc b/sdk/android/src/jni/hardwarevideoencoderfactory.cc
index ebd9b0a..6404c18 100644
--- a/sdk/android/src/jni/hardwarevideoencoderfactory.cc
+++ b/sdk/android/src/jni/hardwarevideoencoderfactory.cc
@@ -23,8 +23,8 @@
     const JavaParamRef<jclass>&,
     const JavaParamRef<jobject>& params1,
     const JavaParamRef<jobject>& params2) {
-  return H264::IsSameH264Profile(JavaToStdMapStrings(jni, params1),
-                                 JavaToStdMapStrings(jni, params2));
+  return H264::IsSameH264Profile(JavaToNativeStringMap(jni, params1),
+                                 JavaToNativeStringMap(jni, params2));
 }
 
 }  // namespace jni
diff --git a/sdk/android/src/jni/videocodecinfo.cc b/sdk/android/src/jni/videocodecinfo.cc
index 0ddc328..e467e5f 100644
--- a/sdk/android/src/jni/videocodecinfo.cc
+++ b/sdk/android/src/jni/videocodecinfo.cc
@@ -20,8 +20,8 @@
 SdpVideoFormat VideoCodecInfoToSdpVideoFormat(JNIEnv* jni,
                                               const JavaRef<jobject>& j_info) {
   return SdpVideoFormat(
-      JavaToStdString(jni, Java_VideoCodecInfo_getName(jni, j_info)),
-      JavaToStdMapStrings(jni, Java_VideoCodecInfo_getParams(jni, j_info)));
+      JavaToNativeString(jni, Java_VideoCodecInfo_getName(jni, j_info)),
+      JavaToNativeStringMap(jni, Java_VideoCodecInfo_getParams(jni, j_info)));
 }
 
 ScopedJavaLocalRef<jobject> SdpVideoFormatToVideoCodecInfo(