diff --git a/android-template/.gitignore b/android-template/.gitignore
new file mode 100644
index 000000000..5b4f013b1
--- /dev/null
+++ b/android-template/.gitignore
@@ -0,0 +1,36 @@
+# The user should review this list, because some of this files can be keep
+# inside their git repo when working in a real project. They are excluded
+# here only to keep webview repo light.
+
+# IDE stuff (usually some of these files are kept in real projects!)
+.idea/
+gradlew
+gradlew.bat
+gradle-wrapper.jar
+gradle-wrapper.properties
+
+# User stuff
+local.properties
+
+# Generated files
+.DS_Store
+/build
+app/.cxx
+
+# Gradle
+.gradle
+
+# Gradle and Maven with auto-import
+*.iml
+*.ipr
+
+# Google
+.google/
+
+# Eclipse
+.classpath
+.project
+org.eclipse.buildship.core.prefs
+
+# Other
+.ionide
\ No newline at end of file
diff --git a/android-template/README.md b/android-template/README.md
new file mode 100644
index 000000000..09712f2c6
--- /dev/null
+++ b/android-template/README.md
@@ -0,0 +1,40 @@
+# Native Activity Webview Template
+
+This sample uses JNI to instantiate an Android Webview from C code.
+
+## Requisites
+
+- [Android Studio](http://developer.android.com/sdk/index.html)
+- [Android NDK](https://developer.android.com/ndk/)
+- [CMake plugin](http://tools.android.com/tech-docs/external-c-builds)
+
+## Instructions
+
+1. Place `webview.h` inside `app/src/main/cpp` directory.
+2. Place custom code (eg. `main.cpp`) in the same folder.
+3. Open project with Android Studio.
+4. Set NDK in `File/Project Structure...`.
+5. Sync Project with Gradle Files.
+6. Connect a device with ADB.
+7. Compile and run.
+
+## Implementation
+
+Code written by the user should contain at least the following lines:
+
+```cpp
+#include "webview.h"
+
+ifdef ANDROID
+int android_main(void *android_app) {
+ webview::webview w(true, android_app);
+#else
+int main() {
+ webview::webview w(true, nullptr);
+#endif
+ /* user code goes here! */
+ return 0;
+}
+```
+
+Android entrypoint `android_main` is expected by the library.
diff --git a/android-template/app/.gitignore b/android-template/app/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/android-template/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android-template/app/build.gradle b/android-template/app/build.gradle
new file mode 100644
index 000000000..9226032c2
--- /dev/null
+++ b/android-template/app/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 29
+ ndkVersion '21.2.6472646'
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+ defaultConfig {
+ applicationId 'ar.net.rainbyte.webview'
+ minSdkVersion 14
+ targetSdkVersion 29
+ versionCode 1
+ versionName "0.0.1"
+ }
+ externalNativeBuild {
+ cmake {
+ path "src/main/cpp/CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/android-template/app/src/main/AndroidManifest.xml b/android-template/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..3a1299272
--- /dev/null
+++ b/android-template/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android-template/app/src/main/assets/index.html b/android-template/app/src/main/assets/index.html
new file mode 100644
index 000000000..72d955e36
--- /dev/null
+++ b/android-template/app/src/main/assets/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Hello Webview for Android
+
+
+
+ Hello NativeActivity Webview!
+
+
+
\ No newline at end of file
diff --git a/android-template/app/src/main/cpp/CMakeLists.txt b/android-template/app/src/main/cpp/CMakeLists.txt
new file mode 100644
index 000000000..bb80628b4
--- /dev/null
+++ b/android-template/app/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Werror")
+
+add_library(webview-android-jni SHARED
+ main.cpp
+ webview.h)
+
+# Include libraries needed for hello-jni lib
+target_link_libraries(webview-android-jni
+ android
+ log)
diff --git a/android-template/app/src/main/cpp/main.cpp b/android-template/app/src/main/cpp/main.cpp
new file mode 100644
index 000000000..0db81e313
--- /dev/null
+++ b/android-template/app/src/main/cpp/main.cpp
@@ -0,0 +1,30 @@
+#include "webview.h"
+
+#ifdef ANDROID
+int android_main(void *android_app) {
+ webview::webview w(true, android_app);
+#elif WIN32
+int WINAPI WinMain(HINSTANCE hInt, HINSTANCE hPrevInst, LPSTR lpCmdLine,
+ int nCmdShow) {
+ webview::webview w(true, nullptr);
+#else
+int main() {
+ webview::webview w(true, nullptr);
+#endif
+ w.init("(function() { console.log('this is init 1, inside function'); })()");
+ w.set_title("Minimal example"); // ignored, not implemented yet
+ w.set_size(480, 320, WEBVIEW_HINT_NONE); // ignored, not implemented yet
+ w.init("console.log('this is init 2');");
+ w.bind("noop", [](std::string noopMsg) -> std::string {
+ return noopMsg;
+ });
+ w.bind("foo", [&w](std::string fooMsg) -> std::string {
+ w.eval("document.querySelector('h1').style.background = 'green';");
+ return fooMsg;
+ });
+ w.init("console.log('this is init 3');");
+ w.navigate("index.html");
+ w.eval("console.log('this is eval');");
+ w.run();
+ return 0;
+}
diff --git a/android-template/app/src/main/java/ar/net/rainbyte/webview/ExternalInvoker.java b/android-template/app/src/main/java/ar/net/rainbyte/webview/ExternalInvoker.java
new file mode 100644
index 000000000..f1a997c30
--- /dev/null
+++ b/android-template/app/src/main/java/ar/net/rainbyte/webview/ExternalInvoker.java
@@ -0,0 +1,16 @@
+package ar.net.rainbyte.webview;
+
+import android.webkit.JavascriptInterface;
+
+public class ExternalInvoker {
+ private NativeFunction mCallback;
+
+ public ExternalInvoker(long funPtr) {
+ mCallback = new NativeFunction(funPtr);
+ }
+
+ @JavascriptInterface
+ public void invoke(String msg) {
+ mCallback.run(msg);
+ }
+}
diff --git a/android-template/app/src/main/java/ar/net/rainbyte/webview/NativeFunction.java b/android-template/app/src/main/java/ar/net/rainbyte/webview/NativeFunction.java
new file mode 100644
index 000000000..6ef87f3f5
--- /dev/null
+++ b/android-template/app/src/main/java/ar/net/rainbyte/webview/NativeFunction.java
@@ -0,0 +1,18 @@
+package ar.net.rainbyte.webview;
+
+public class NativeFunction {
+ static {
+ System.loadLibrary("webview-android-jni");
+ }
+ public native void runFunction(long funPtr, Object ... args);
+
+ long mFunPtr;
+
+ public NativeFunction(long funPtr) {
+ mFunPtr = funPtr;
+ }
+
+ public void run(Object ... args) {
+ runFunction(mFunPtr, args);
+ }
+}
diff --git a/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_bg.xml b/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_bg.xml
new file mode 100644
index 000000000..97fcf8941
--- /dev/null
+++ b/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_bg.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_fg.xml b/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_fg.xml
new file mode 100644
index 000000000..64925774a
--- /dev/null
+++ b/android-template/app/src/main/res/drawable-anydpi-v21/ic_launcher_fg.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..075e83f67
--- /dev/null
+++ b/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..075e83f67
--- /dev/null
+++ b/android-template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android-template/app/src/main/res/mipmap/ic_launcher.png b/android-template/app/src/main/res/mipmap/ic_launcher.png
new file mode 100644
index 000000000..f3729604d
Binary files /dev/null and b/android-template/app/src/main/res/mipmap/ic_launcher.png differ
diff --git a/android-template/app/src/main/res/values-w820dp/dimens.xml b/android-template/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 000000000..63fc81644
--- /dev/null
+++ b/android-template/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/android-template/app/src/main/res/values/dimens.xml b/android-template/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..47c822467
--- /dev/null
+++ b/android-template/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/android-template/app/src/main/res/values/strings.xml b/android-template/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..8f2292ba7
--- /dev/null
+++ b/android-template/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ WebviewAndroidJni
+
diff --git a/android-template/build.gradle b/android-template/build.gradle
new file mode 100644
index 000000000..4438d8a7c
--- /dev/null
+++ b/android-template/build.gradle
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.0.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android-template/gradle.properties b/android-template/gradle.properties
new file mode 100644
index 000000000..e78e65c08
--- /dev/null
+++ b/android-template/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+android.useAndroidX=true
diff --git a/android-template/settings.gradle b/android-template/settings.gradle
new file mode 100644
index 000000000..cbff0d8a5
--- /dev/null
+++ b/android-template/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='AndroidWebviewJni'
+include ':app'
diff --git a/webview.h b/webview.h
index f4f9ecdec..b100dc320 100644
--- a/webview.h
+++ b/webview.h
@@ -113,8 +113,11 @@ WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
#ifndef WEBVIEW_HEADER
-#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
-#if defined(__linux__)
+#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && \
+ !defined(WEBVIEW_EDGE) && !defined(WEBVIEW_ANDROID)
+#if defined(__ANDROID__)
+#define WEBVIEW_ANDROID
+#elif defined(__linux__)
#define WEBVIEW_GTK
#elif defined(__APPLE__)
#define WEBVIEW_COCOA
@@ -1138,7 +1141,454 @@ class win32_edge_engine {
using browser_engine = win32_edge_engine;
} // namespace webview
-#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
+#elif defined(WEBVIEW_ANDROID)
+//
+// ====================================================================
+//
+// This implementation uses Android backend. It requires to be placed
+// inside an Android project preconfigured to be compatible.
+//
+// ====================================================================
+//
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+#define LOGE(...) \
+ ((void)__android_log_print(ANDROID_LOG_ERROR, "webview-android-jni", \
+ __VA_ARGS__))
+#define LOGI(...) \
+ ((void)__android_log_print(ANDROID_LOG_INFO, "webview-android-jni", \
+ __VA_ARGS__))
+
+#include
+
+/* For debug builds, always enable the debug traces in this library */
+#ifndef NDEBUG
+#define LOGV(...) \
+ ((void)__android_log_print(ANDROID_LOG_VERBOSE, "webview-android-jni", \
+ __VA_ARGS__))
+#else
+#define LOGV(...) ((void)0)
+#endif
+
+#include
+
+extern int android_main(void *app);
+
+namespace webview {
+
+// Use correct ClassLoader when FindClass context changed (eg. on threads launched by hand)
+jclass retrieveClass(JNIEnv *jni, ANativeActivity *activity,
+ const char *className) {
+ jclass activityClass = jni->FindClass("android/app/NativeActivity");
+ jobject loader = jni->CallObjectMethod(
+ activity->clazz, jni->GetMethodID(activityClass, "getClassLoader",
+ "()Ljava/lang/ClassLoader;"));
+ jclass classLoader = jni->FindClass("java/lang/ClassLoader");
+ jstring strClassName = jni->NewStringUTF(className);
+ jclass classRetrieved = (jclass)jni->CallObjectMethod(
+ loader,
+ jni->GetMethodID(classLoader, "loadClass",
+ "(Ljava/lang/String;)Ljava/lang/Class;"),
+ strClassName);
+ jni->DeleteLocalRef(strClassName);
+ return classRetrieved;
+}
+
+std::string jstring2string(JNIEnv *env, jstring jstr) {
+ if (!jstr)
+ return "";
+
+ const jclass classString = env->GetObjectClass(jstr);
+ const jbyteArray jbytes = (jbyteArray)env->CallObjectMethod(
+ jstr, env->GetMethodID(classString, "getBytes", "(Ljava/lang/String;)[B"),
+ env->NewStringUTF("UTF-8"));
+
+ size_t length = static_cast(env->GetArrayLength(jbytes));
+ jbyte *pbytes = env->GetByteArrayElements(jbytes, nullptr);
+
+ std::string res = std::string(reinterpret_cast(pbytes), length);
+ env->ReleaseByteArrayElements(jbytes, pbytes, JNI_ABORT);
+
+ env->DeleteLocalRef(jbytes);
+ env->DeleteLocalRef(classString);
+
+ return res;
+}
+
+struct android_app {
+ ANativeActivity *aNativeActivity;
+ void *savedState;
+ size_t savedStateSize;
+ ALooper *looper;
+ // below are things used by webview.h
+ int msgread;
+ int msgwrite;
+ void *tasks;
+ void *tasks_mutex;
+ void *cond;
+ jobject webview;
+ int running;
+};
+
+static ALooper *mainThreadLooper;
+static int android_app_callback(int fd, int events, void *data);
+
+android_app *create_android_app(ANativeActivity *activity, void *savedState,
+ size_t savedStateSize) {
+ android_app *android_app = new struct android_app();
+ android_app->aNativeActivity = activity;
+
+ if (savedState != nullptr) {
+ android_app->savedState = malloc(savedStateSize);
+ android_app->savedStateSize = savedStateSize;
+ memcpy(android_app->savedState, savedState, savedStateSize);
+ }
+
+ mainThreadLooper = ALooper_forThread(); // get looper for this thread
+ ALooper_acquire(mainThreadLooper); // add reference to keep object alive
+ // listen for pipe read end, if there is something to read
+ // - notify via provided callback on main thread
+ int msgpipe[2];
+ if (pipe(msgpipe)) {
+ LOGE("could not create pipe: %s", strerror(errno));
+ return nullptr;
+ }
+ android_app->msgread = msgpipe[0];
+ android_app->msgwrite = msgpipe[1];
+
+ android_app->tasks = new std::deque>;
+ android_app->tasks_mutex = new std::mutex();
+
+ ALooper_addFd(mainThreadLooper, android_app->msgread, 0, ALOOPER_EVENT_INPUT,
+ android_app_callback, android_app);
+ LOGI("fd is registered");
+
+ auto cond = new std::condition_variable();
+ android_app->cond = cond;
+
+ std::thread appThread([android_app]() {
+ ALooper *looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ android_app->looper = looper;
+
+ auto tasks_mutex = static_cast(android_app->tasks_mutex);
+ std::unique_lock lock(*tasks_mutex);
+ android_app->running = 1;
+ lock.unlock();
+ static_cast(android_app->cond)->notify_all();
+
+ android_main(android_app);
+ });
+ appThread.detach();
+
+ auto tasks_mutex = static_cast(android_app->tasks_mutex);
+ std::unique_lock lock(*tasks_mutex);
+ cond->wait(lock, [android_app]() { return !android_app->running; });
+
+ return android_app;
+}
+
+static int android_app_callback(int fd, int events, void *data) {
+ LOGI("app callback was called");
+ char msg;
+ read(fd, &msg, 1);
+
+ auto app = static_cast(data);
+ auto tasks_mutex = static_cast(app->tasks_mutex);
+ auto tasks =
+ static_cast> *>(app->tasks);
+ std::unique_lock lock(*tasks_mutex);
+
+ lock.unlock();
+ while (!tasks->empty()) {
+ auto task(std::move(tasks->front()));
+ tasks->pop_front();
+ task();
+ }
+ lock.lock();
+
+ return 1; // continue listening
+}
+
+class android_engine {
+public:
+ android_engine(bool debug, void *data) {
+ m_android_app = static_cast(data);
+ m_tasks_mutex = static_cast(m_android_app->tasks_mutex);
+ m_tasks = static_cast> *>(
+ m_android_app->tasks);
+
+ auto app = m_android_app;
+
+ runOnUiThread([this, app]() {
+ LOGI("create was called");
+ auto aNativeActivity =
+ static_cast(app->aNativeActivity);
+ aNativeActivity->vm->AttachCurrentThread(&aNativeActivity->env, nullptr);
+
+ JNIEnv *env = aNativeActivity->env;
+ jobject activity =
+ aNativeActivity->clazz; // it is a jobject, even if it says "clazz"
+
+ jclass classNativeActivity = env->GetObjectClass(activity);
+ jclass classActivity = env->GetSuperclass(classNativeActivity);
+ jclass classWebview = env->FindClass("android/webkit/WebView");
+
+ // Create WebView instance and add it to Activity contentView
+ jobject webview =
+ env->NewObject(classWebview,
+ env->GetMethodID(classWebview, "",
+ "(Landroid/content/Context;)V"),
+ activity);
+ env->CallNonvirtualVoidMethod(activity, classActivity,
+ env->GetMethodID(classActivity,
+ "setContentView",
+ "(Landroid/view/View;)V"),
+ webview);
+
+ // Enable JavaScript
+ jobject webSettings = env->CallNonvirtualObjectMethod(
+ webview, classWebview,
+ env->GetMethodID(classWebview, "getSettings",
+ "()Landroid/webkit/WebSettings;"));
+ jclass classWebSettings = env->GetObjectClass(webSettings);
+ env->CallNonvirtualVoidMethod(
+ webSettings, classWebSettings,
+ env->GetMethodID(classWebSettings, "setJavaScriptEnabled", "(Z)V"),
+ JNI_TRUE);
+
+ // Inject external.invoke function
+ jclass classExternalInvoker = retrieveClass(
+ env, aNativeActivity, "ar/net/rainbyte/webview/ExternalInvoker");
+ jobject invoker = env->NewObject(
+ classExternalInvoker,
+ env->GetMethodID(classExternalInvoker, "", "(J)V"),
+ &m_invoke_callback);
+ env->CallNonvirtualVoidMethod(
+ webview, classWebview,
+ env->GetMethodID(classWebview, "addJavascriptInterface",
+ "(Ljava/lang/Object;Ljava/lang/String;)V"),
+ invoker, env->NewStringUTF("external"));
+
+ // save as global ref to avoid gc cleanups
+ app->webview = env->NewGlobalRef(webview);
+ });
+ }
+ virtual ~android_engine() { destroy(); }
+ void *window() { return m_android_app; }
+ void run() {
+ while (true) {
+ int ident;
+ int events;
+ void *data;
+ while ((ident = ALooper_pollAll(-1, nullptr, &events, &data)) >= 0) {
+ }
+ }
+ }
+ void destroy() {
+ auto app = m_android_app;
+ runOnUiThread([app]() {
+ JNIEnv *env = app->aNativeActivity->env;
+ jobject activity =
+ app->aNativeActivity
+ ->clazz; // it is a jobject, even if it says "clazz"
+ env->DeleteGlobalRef(activity);
+ env->DeleteGlobalRef(app->webview);
+ });
+ }
+ void terminate() {
+ // TODO unimplemented method
+ }
+ void dispatch(std::function f) { runOnUiThread(f); }
+ void set_title(const std::string title) {
+ // TODO unimplemented method
+ }
+ void set_size(int width, int height, int hints) {
+ // TODO unimplemented method
+ }
+
+ virtual void navigate(const std::string url) {
+ auto app = m_android_app;
+ runOnUiThread([app, url, this]() {
+ LOGI("navigate was called");
+ if (app->webview == nullptr)
+ return;
+
+ JNIEnv *env = app->aNativeActivity->env;
+ jobject activity =
+ app->aNativeActivity
+ ->clazz; // it is a jobject, even if it says "clazz"
+
+ jclass classActivity = env->FindClass("android/app/Activity");
+ jclass classWebview = env->FindClass("android/webkit/WebView");
+
+ if (url.rfind("http", 0) == 0) {
+ // Open remote resource (FIXME: validation of Android permissions is missing)
+ env->CallNonvirtualVoidMethod(
+ app->webview, classWebview,
+ env->GetMethodID(classWebview, "loadUrl", "(Ljava/lang/String;)V"),
+ env->NewStringUTF(url.c_str()));
+
+ // FIXME: js could be executed after onload if injection is done this way :(
+ for (auto script : m_scripts) {
+ eval(script);
+ }
+ } else {
+ std::string htmlStr;
+ if (url.rfind("data:text/text,", 0) == 0 ||
+ url.rfind("data:text/html,", 0) == 0) {
+ htmlStr.append(url.substr(15));
+ } else {
+ // Open web asset from app data folder (it should be relative to "file:///android_asset/")
+ jobject assetManagerObj = env->CallObjectMethod(
+ activity,
+ env->GetMethodID(classActivity, "getAssets",
+ "()Landroid/content/res/AssetManager;"));
+ AAssetManager *assetManager =
+ AAssetManager_fromJava(env, assetManagerObj);
+ AAsset *file =
+ AAssetManager_open(assetManager, url.c_str(), AASSET_MODE_BUFFER);
+ size_t fileLength = AAsset_getLength(file);
+ std::vector fileContent;
+ fileContent.resize(fileLength + 1);
+ AAsset_read(file, &fileContent[0], fileLength);
+ fileContent[fileLength] = '\0';
+ htmlStr.append(fileContent.data());
+ }
+
+ /* FIXME: this is a HACK to inject JS init scripts, avoiding HTML parsing
+ * Android SDK doesn't have a way to execute JS before window.onload, it provides a callback
+ * called "onPageFinished" but onload is executed first, so as workaround \n");
+ }
+
+ LOGI("navigate.htmlStr=%s", htmlStr.c_str());
+
+ env->CallVoidMethod(
+ app->webview,
+ env->GetMethodID(
+ classWebview, "loadData",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
+ env->NewStringUTF(htmlStr.c_str()), env->NewStringUTF("text/html"),
+ env->NewStringUTF("UTF-8"));
+ }
+ });
+ }
+ void init(const std::string js) { m_scripts.push_back(js); }
+ void eval(const std::string js) {
+ auto app = m_android_app;
+ runOnUiThread([app, js]() {
+ LOGI("eval.js=%s", js.c_str());
+ if (app->webview == nullptr)
+ return;
+
+ JNIEnv *env = app->aNativeActivity->env;
+ jclass classWebview = env->FindClass("android/webkit/WebView");
+ env->CallNonvirtualVoidMethod(
+ app->webview, classWebview,
+ env->GetMethodID(
+ classWebview, "evaluateJavascript",
+ "(Ljava/lang/String;Landroid/webkit/ValueCallback;)V"),
+ env->NewStringUTF(js.c_str()), nullptr);
+ });
+ }
+
+private:
+ virtual void on_message(const std::string msg) = 0;
+
+ std::function
+ m_invoke_callback = [this](JNIEnv *env, jobject thiz, jobjectArray args) {
+ jstring jstr =
+ reinterpret_cast(env->GetObjectArrayElement(args, 0));
+ std::string msg = jstring2string(env, jstr);
+ LOGI("invoke lambda callback, msg = %s", msg.c_str());
+ on_message(msg);
+ };
+
+ android_app *m_android_app;
+ std::mutex *m_tasks_mutex;
+ std::deque> *m_tasks;
+
+ std::vector m_scripts;
+
+ void runOnUiThread(std::function f) {
+ std::packaged_task task(f);
+
+ {
+ std::lock_guard lock(*m_tasks_mutex);
+ m_tasks->push_back(std::move(task));
+ }
+
+ char cmd = 1;
+ if (write(m_android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
+ LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
+ }
+ }
+};
+
+using browser_engine = android_engine;
+
+} // namespace webview
+
+JNIEXPORT
+void ANativeActivity_onCreate(ANativeActivity *aNativeActivity,
+ void *savedState, size_t savedStateSize) {
+ LOGV("Creating: %p\n", aNativeActivity);
+
+ aNativeActivity->vm->AttachCurrentThread(&aNativeActivity->env, nullptr);
+
+ JNIEnv *env = aNativeActivity->env;
+ jobject activity =
+ aNativeActivity->clazz; // it is a jobject, even if it says "clazz"
+
+ jclass classNativeActivity = env->GetObjectClass(activity);
+ jclass classActivity = env->GetSuperclass(classNativeActivity);
+
+ // Take full control of native window surface and input queue
+ jobject windowObj = env->CallNonvirtualObjectMethod(
+ activity, classActivity,
+ env->GetMethodID(classActivity, "getWindow", "()Landroid/view/Window;"));
+ jclass classWindow = env->GetObjectClass(windowObj);
+ env->CallNonvirtualVoidMethod(
+ windowObj, classWindow,
+ env->GetMethodID(classWindow, "takeSurface",
+ "(Landroid/view/SurfaceHolder$Callback2;)V"),
+ nullptr);
+ env->CallNonvirtualVoidMethod(
+ windowObj, classWindow,
+ env->GetMethodID(classWindow, "takeInputQueue",
+ "(Landroid/view/InputQueue$Callback;)V"),
+ nullptr);
+
+ // Call superclass (ie. Activity) onCreate to populate Java-based contentView tree
+ env->CallNonvirtualVoidMethod(
+ activity, classActivity,
+ env->GetMethodID(classActivity, "onCreate", "(Landroid/os/Bundle;)V"),
+ nullptr);
+
+ aNativeActivity->instance =
+ webview::create_android_app(aNativeActivity, savedState, savedStateSize);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_ar_net_rainbyte_webview_NativeFunction_runFunction(JNIEnv *env,
+ jobject thiz,
+ jlong fun_ptr,
+ jobjectArray args) {
+ (*reinterpret_cast *>(
+ fun_ptr))(env, thiz, args);
+}
+
+#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE, WEBVIEW_ANDROID */
namespace webview {