Android: Replacement for JNIEnv::FindClass that works from any thread

This CL adds a replacement for JNIEnv::FindClass that works from any
thread, i.e. from native C++ threads as well. This function will be used
from the generated JNI code. Long term, we should stop using
classreferenceholder that relies on a hardcoded list of WebRTC classes.

Bug: webrtc:8278
Change-Id: I4f40c744325ac02b73bd8fa479ab50b684429dc2
Reviewed-on: https://webrtc-review.googlesource.com/20223
Commit-Queue: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20583}
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index f30c7ae..b2bef9e 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -33,9 +33,19 @@
   }
 }
 
+generate_jni("generated_base_jni") {
+  sources = [
+    "src/java/org/webrtc/ClassLoader.java",
+  ]
+  jni_package = ""
+  jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"
+}
+
 rtc_source_set("base_jni") {
   sources = [
     "src/jni/androidhistogram_jni.cc",
+    "src/jni/class_loader.cc",
+    "src/jni/class_loader.h",
     "src/jni/classreferenceholder.cc",
     "src/jni/classreferenceholder.h",
     "src/jni/jni_common.cc",
@@ -49,6 +59,7 @@
   ]
 
   deps = [
+    ":generated_base_jni",
     "../../api:libjingle_peerconnection_api",
     "../../rtc_base:rtc_base",
     "../../rtc_base:rtc_base_approved",
@@ -465,6 +476,7 @@
     "src/java/org/webrtc/Camera2Session.java",
     "src/java/org/webrtc/CameraCapturer.java",
     "src/java/org/webrtc/CameraSession.java",
+    "src/java/org/webrtc/ClassLoader.java",
     "src/java/org/webrtc/DynamicBitrateAdjuster.java",
     "src/java/org/webrtc/EglBase10.java",
     "src/java/org/webrtc/EglBase14.java",
diff --git a/sdk/android/src/java/org/webrtc/ClassLoader.java b/sdk/android/src/java/org/webrtc/ClassLoader.java
new file mode 100644
index 0000000..4637046
--- /dev/null
+++ b/sdk/android/src/java/org/webrtc/ClassLoader.java
@@ -0,0 +1,23 @@
+/*
+ *  Copyright 2017 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;
+
+/**
+ * This class provides a ClassLoader that is capable of loading WebRTC Java classes regardless of
+ * what thread it's called from. Such a ClassLoader is needed for the few cases where the JNI
+ * mechanism is unable to automatically determine the appropriate ClassLoader instance.
+ */
+class ClassLoader {
+  @CalledByNative
+  static Object getClassLoader() {
+    return ClassLoader.class.getClassLoader();
+  }
+}
diff --git a/sdk/android/src/jni/class_loader.cc b/sdk/android/src/jni/class_loader.cc
new file mode 100644
index 0000000..c21e6c0
--- /dev/null
+++ b/sdk/android/src/jni/class_loader.cc
@@ -0,0 +1,80 @@
+/*
+ *  Copyright 2017 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 "sdk/android/src/jni/class_loader.h"
+
+#include <algorithm>
+#include <string>
+
+#include "rtc_base/checks.h"
+#include "sdk/android/generated_base_jni/jni/ClassLoader_jni.h"
+
+// Abort the process if |jni| has a Java exception pending. This macros uses the
+// comma operator to execute ExceptionDescribe and ExceptionClear ignoring their
+// return values and sending "" to the error stream.
+#define CHECK_EXCEPTION(jni)        \
+  RTC_CHECK(!jni->ExceptionCheck()) \
+      << (jni->ExceptionDescribe(), jni->ExceptionClear(), "")
+
+namespace webrtc {
+namespace jni {
+
+namespace {
+
+class ClassLoader {
+ public:
+  explicit ClassLoader(JNIEnv* env) {
+    class_loader_class_ = reinterpret_cast<jclass>(
+        env->NewGlobalRef(env->FindClass("java/lang/ClassLoader")));
+    CHECK_EXCEPTION(env);
+    load_class_method_ =
+        env->GetMethodID(class_loader_class_, "loadClass",
+                         "(Ljava/lang/String;)Ljava/lang/Class;");
+    CHECK_EXCEPTION(env);
+    class_loader_ = env->NewGlobalRef(Java_ClassLoader_getClassLoader(env));
+    CHECK_EXCEPTION(env);
+  }
+
+  jclass FindClass(JNIEnv* env, const char* c_name) {
+    // ClassLoader.loadClass expects a classname with components separated by
+    // dots instead of the slashes that JNIEnv::FindClass expects.
+    std::string name(c_name);
+    std::replace(name.begin(), name.end(), '/', '.');
+    jstring jstr = env->NewStringUTF(name.c_str());
+    const jclass clazz = static_cast<jclass>(
+        env->CallObjectMethod(class_loader_, load_class_method_, jstr));
+    CHECK_EXCEPTION(env);
+    return clazz;
+  }
+
+ private:
+  jclass class_loader_class_;
+  jmethodID load_class_method_;
+  jobject class_loader_;
+};
+
+static ClassLoader* g_class_loader = nullptr;
+
+}  // namespace
+
+void InitClassLoader(JNIEnv* env) {
+  RTC_CHECK(g_class_loader == nullptr);
+  g_class_loader = new ClassLoader(env);
+}
+
+jclass GetClass(JNIEnv* env, const char* name) {
+  // The class loader will be null in the JNI code called from the ClassLoader
+  // ctor when we are bootstrapping ourself.
+  return (g_class_loader == nullptr) ? env->FindClass(name)
+                                     : g_class_loader->FindClass(env, name);
+}
+
+}  // namespace jni
+}  // namespace webrtc
diff --git a/sdk/android/src/jni/class_loader.h b/sdk/android/src/jni/class_loader.h
new file mode 100644
index 0000000..52bb1ba
--- /dev/null
+++ b/sdk/android/src/jni/class_loader.h
@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2017 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's FindClass() is tricky because the app-specific ClassLoader is not
+// consulted when there is no app-specific frame on the stack (i.e. when called
+// from a thread created from native C++ code). These helper functions provide a
+// workaround for this.
+// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
+
+#ifndef SDK_ANDROID_SRC_JNI_CLASS_LOADER_H_
+#define SDK_ANDROID_SRC_JNI_CLASS_LOADER_H_
+
+#include <jni.h>
+
+namespace webrtc {
+namespace jni {
+
+// This method should be called from JNI_OnLoad and before any calls to
+// FindClass.
+void InitClassLoader(JNIEnv* env);
+
+// This function is identical to JNIEnv::FindClass except that it works from any
+// thread. This function loads and returns a local reference to the class with
+// the given name. The name argument is a fully-qualified class name. For
+// example, the fully-qualified class name for the java.lang.String class is:
+// "java/lang/String". This function will be used from the JNI generated code
+// and should rarely be used manually.
+jclass GetClass(JNIEnv* env, const char* name);
+
+}  // namespace jni
+}  // namespace webrtc
+
+#endif  // SDK_ANDROID_SRC_JNI_CLASS_LOADER_H_
diff --git a/sdk/android/src/jni/classreferenceholder.cc b/sdk/android/src/jni/classreferenceholder.cc
index 3943cf2..e4ff16f 100644
--- a/sdk/android/src/jni/classreferenceholder.cc
+++ b/sdk/android/src/jni/classreferenceholder.cc
@@ -111,7 +111,6 @@
   LoadClass(jni, "org/webrtc/VideoCodecStatus");
   LoadClass(jni, "org/webrtc/VideoDecoder$Settings");
   LoadClass(jni, "org/webrtc/VideoDecoderWrapperCallback");
-  LoadClass(jni, "org/webrtc/VideoEncoder");
   LoadClass(jni, "org/webrtc/VideoEncoder$BitrateAllocation");
   LoadClass(jni, "org/webrtc/VideoEncoder$EncodeInfo");
   LoadClass(jni, "org/webrtc/VideoEncoder$ScalingSettings");
diff --git a/sdk/android/src/jni/classreferenceholder.h b/sdk/android/src/jni/classreferenceholder.h
index 7d8658b..5145c10 100644
--- a/sdk/android/src/jni/classreferenceholder.h
+++ b/sdk/android/src/jni/classreferenceholder.h
@@ -16,6 +16,9 @@
 #ifndef SDK_ANDROID_SRC_JNI_CLASSREFERENCEHOLDER_H_
 #define SDK_ANDROID_SRC_JNI_CLASSREFERENCEHOLDER_H_
 
+// TODO(magjed): Remove this whole file and replace with either generated JNI
+// code or class_loader.h.
+
 #include <jni.h>
 #include <map>
 #include <string>
@@ -28,6 +31,9 @@
 // FreeGlobalClassReferenceHolder must be called in JNI_UnLoad.
 void FreeGlobalClassReferenceHolder();
 
+// Deprecated. Most cases of finding classes should be done with generated JNI
+// code, and the few remaining cases should use the function from
+// class_loader.h.
 // Returns a global reference guaranteed to be valid for the lifetime of the
 // process.
 jclass FindClass(JNIEnv* jni, const char* name);
diff --git a/sdk/android/src/jni/jni_generator_helper.cc b/sdk/android/src/jni/jni_generator_helper.cc
index 5a09523..c870e1c 100644
--- a/sdk/android/src/jni/jni_generator_helper.cc
+++ b/sdk/android/src/jni/jni_generator_helper.cc
@@ -11,22 +11,11 @@
 #include "sdk/android/src/jni/jni_generator_helper.h"
 
 #include "rtc_base/atomicops.h"
-#include "sdk/android/src/jni/classreferenceholder.h"
+#include "sdk/android/src/jni/class_loader.h"
 
 namespace base {
 namespace android {
 
-namespace {
-// JNIEnv-helper methods that RTC_CHECK success: no Java exception thrown and
-// found object/class/method/field is non-null.
-jclass GetClass(JNIEnv* jni, const char* class_name) {
-  jclass clazz = webrtc::jni::FindClass(jni, class_name);
-  CHECK_EXCEPTION(jni) << "error during FindClass: " << class_name;
-  RTC_CHECK(clazz) << class_name;
-  return clazz;
-}
-}  // namespace
-
 // If |atomic_class_id| set, it'll return immediately. Otherwise, it will look
 // up the class and store it. If there's a race, we take care to only store one
 // global reference (and the duplicated effort will happen only once).
@@ -39,8 +28,9 @@
       rtc::AtomicOps::AcquireLoadPtr(atomic_class_id);
   if (value)
     return reinterpret_cast<jclass>(value);
-  jclass clazz =
-      static_cast<jclass>(env->NewGlobalRef(GetClass(env, class_name)));
+  jclass clazz = static_cast<jclass>(
+      env->NewGlobalRef(webrtc::jni::GetClass(env, class_name)));
+  RTC_CHECK(clazz) << class_name;
   base::subtle::AtomicWord null_aw = nullptr;
   base::subtle::AtomicWord cas_result = rtc::AtomicOps::CompareAndSwapPtr(
       atomic_class_id, null_aw,
diff --git a/sdk/android/src/jni/jni_onload.cc b/sdk/android/src/jni/jni_onload.cc
index 81cc488..cb708e7 100644
--- a/sdk/android/src/jni/jni_onload.cc
+++ b/sdk/android/src/jni/jni_onload.cc
@@ -13,6 +13,7 @@
 #define JNIEXPORT __attribute__((visibility("default")))
 
 #include "rtc_base/ssladapter.h"
+#include "sdk/android/src/jni/class_loader.h"
 #include "sdk/android/src/jni/classreferenceholder.h"
 #include "sdk/android/src/jni/jni_helpers.h"
 
@@ -27,6 +28,8 @@
 
   RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
   LoadGlobalClassReferenceHolder();
+  JNIEnv* env = AttachCurrentThreadIfNeeded();
+  InitClassLoader(env);
 
   return ret;
 }