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 {