From 113b4641d4d86adabbb857e4c777cc1d24f3ed39 Mon Sep 17 00:00:00 2001 From: Alexander Mahr Date: Sun, 2 Mar 2025 09:22:36 +0100 Subject: [PATCH] start webview only branch --- README.md | 8 +- app/src/nativecode/main.c | 370 ++++++++++--------- app/src/nativecode/webview_native_activity.h | 312 ++++++++++++++++ 3 files changed, 511 insertions(+), 179 deletions(-) create mode 100644 app/src/nativecode/webview_native_activity.h diff --git a/README.md b/README.md index 6ccea5c..91dfdb9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ -# Android App Written in C (build via docker/podman) +# Native (non java written) Android App Written in C (build via docker/podman) -This repo provides its user with the ability to setup and compile a Android-App that is -based on `android.app.NativeActiviy` and thus can be completely compiled C source-code. +branch `webview` to only setup a webview -It is an adoptation (and hopefully somehow simplification of this repo [`rawdrawandroid`](https://github.com/cnlohr/rawdrawandroid) -and this many thanks and credits to most of the c-code goes to CNLohr. ## usage @@ -23,6 +20,7 @@ adb install -r app/result/app.apk ## basic ideas +* "native app" to remove need to work with JAVA/KOTLIN madness * work within container (debian based image) * use Makefile as a build tool * avoid the "unhappiness" of having to deal with neither KOTLIN nor JAVA diff --git a/app/src/nativecode/main.c b/app/src/nativecode/main.c index c4d13b0..f19b2f8 100644 --- a/app/src/nativecode/main.c +++ b/app/src/nativecode/main.c @@ -6,16 +6,20 @@ #include #include //why this (related to this OG_* functions)#include "os_generic.h" +//#include "os_generic.h" //#include -//A-s-s-e-t-s #include -//A-s-s-e-t-s #include +//#include +//#include #include -//SENSORSTURFF #include +#include +#define APPNAME "MyApp" + +//#include #include #include #include #include "CNFGAndroid.h" - +#define true 1 //#define CNFA_IMPLEMENTATION #define CNFG_IMPLEMENTATION #define CNFG3D @@ -23,8 +27,8 @@ //AUDIO //#include "cnfa/CNFA.h" #include "CNFG.h" -//webview//#define WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION -//webview//#include "webview_native_activity.h" +#define WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION +#include "webview_native_activity.h" //SENSORSTURFF //Heightmap float mountainangle; //SENSORSTURFF //Heightmap float mountainoffsetx; @@ -36,7 +40,7 @@ //SENSORSTURFF ASensorEventQueue* aeq; //SENSORSTURFF ALooper * l; -//webview//WebViewNativeActivityObject MyWebView; +WebViewNativeActivityObject MyWebView; //AUDIO const uint32_t SAMPLE_RATE = 44100; //AUDIO const uint16_t SAMPLE_COUNT = 512; @@ -355,175 +359,193 @@ void HandleThisWindowTermination() //graphic-random-texture uint32_t randomtexturedata[256*256]; -//webview //uint32_t webviewdata[500*500]; -//webview //char fromJSBuffer[128]; -//webview -//webview //void CheckWebView( void * v ) -//webview //{ -//webview // static int runno = 0; -//webview // WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v; -//webview // if( WebViewGetProgress( wvn ) != 100 ) return; -//webview // -//webview // runno++; -//webview // if( runno == 1 ) -//webview // { -//webview // // The attach (initial) message payload has no meaning. -//webview // WebViewPostMessage( wvn, "", 1 ); -//webview // } -//webview // else -//webview // { -//webview // // Invoke JavaScript, which calls a function to send a webmessage -//webview // // back into C land. -//webview // WebViewExecuteJavascript( wvn, "SendMessageToC();" ); -//webview // -//webview // // Send a WebMessage into the JavaScript code. -//webview // char st[128]; -//webview // sprintf( st, "Into JavaScript %d\n", runno ); -//webview // WebViewPostMessage( wvn, st, 0 ); -//webview // } -//webview //} -//webview//jobject g_attachLooper; +uint32_t * webviewdata;//[500*500]; +//uint32_t webviewdata[3000*2000]; +char fromJSBuffer[128]; -//webview //void SetupWebView( void * v ) -//webview //{ -//webview // WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v; -//webview // -//webview // -//webview // const struct JNINativeInterface * env = 0; -//webview // const struct JNINativeInterface ** envptr = &env; -//webview // const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; -//webview // const struct JNIInvokeInterface * jnii = *jniiptr; -//webview // -//webview // jnii->AttachCurrentThread( jniiptr, &envptr, NULL); -//webview // env = (*envptr); -//webview // -//webview // while( g_attachLooper == 0 ) usleep(1); -//webview // WebViewCreate( wvn, "file:///android_asset/index.html", g_attachLooper, 500, 500 ); -//webview // //WebViewCreate( wvn, "about:blank", g_attachLooper, 500, 500 ); -//webview //} +void CheckWebView( void * v ) +{ + static int runno = 0; + WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v; + if( WebViewGetProgress( wvn ) != 100 ) return; + + runno++; + if( runno == 1 ) + { + // The attach (initial) message payload has no meaning. + WebViewPostMessage( wvn, "", 1 ); + } + else + { + // Invoke JavaScript, which calls a function to send a webmessage + // back into C land. + WebViewExecuteJavascript( wvn, "SendMessageToC();" ); + + // Send a WebMessage into the JavaScript code. + char st[128]; + sprintf( st, "Into JavaScript %d\n", runno ); + WebViewPostMessage( wvn, st, 0 ); + } +} + +jobject g_attachLooper; + +void SetupWebView( void * v ) +{ + WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v; -//webview //pthread_t jsthread; -//webview // -//webview //void * JavscriptThread( void * v ) -//webview //{ -//webview // const struct JNINativeInterface * env = 0; -//webview // const struct JNINativeInterface ** envptr = &env; -//webview // const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; -//webview // const struct JNIInvokeInterface * jnii = *jniiptr; -//webview // -//webview // jnii->AttachCurrentThread( jniiptr, &envptr, NULL); -//webview // env = (*envptr); -//webview // -//webview // // Create a looper on this thread... -//webview // jclass LooperClass = env->FindClass(envptr, "android/os/Looper"); -//webview // jmethodID myLooperMethod = env->GetStaticMethodID(envptr, LooperClass, "myLooper", "()Landroid/os/Looper;"); -//webview // jobject thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod ); -//webview // if( !thisLooper ) -//webview // { -//webview // jmethodID prepareMethod = env->GetStaticMethodID(envptr, LooperClass, "prepare", "()V"); -//webview // env->CallStaticVoidMethod( envptr, LooperClass, prepareMethod ); -//webview // thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod ); -//webview // g_attachLooper = env->NewGlobalRef(envptr, thisLooper); -//webview // } -//webview // -//webview // jmethodID getQueueMethod = env->GetMethodID( envptr, LooperClass, "getQueue", "()Landroid/os/MessageQueue;" ); -//webview // jobject lque = env->CallObjectMethod( envptr, g_attachLooper, getQueueMethod ); -//webview // -//webview // jclass MessageQueueClass = env->FindClass(envptr, "android/os/MessageQueue"); -//webview // jmethodID nextMethod = env->GetMethodID( envptr, MessageQueueClass, "next", "()Landroid/os/Message;" ); -//webview // -//webview // jclass MessageClass = env->FindClass(envptr, "android/os/Message"); -//webview // jfieldID objid = env->GetFieldID( envptr, MessageClass, "obj", "Ljava/lang/Object;" ); -//webview // jclass PairClass = env->FindClass(envptr, "android/util/Pair"); -//webview // jfieldID pairfirst = env->GetFieldID( envptr, PairClass, "first", "Ljava/lang/Object;" ); -//webview // -//webview // while(1) -//webview // { -//webview // // Instead of using Looper::loop(), we just call next on the looper object. -//webview // jobject msg = env->CallObjectMethod( envptr, lque, nextMethod ); -//webview // jobject innerObj = env->GetObjectField( envptr, msg, objid ); -//webview // const char * name; -//webview // jstring strObj; -//webview // jclass innerClass; -//webview // -//webview // // Check Object Type -//webview // { -//webview // innerClass = env->GetObjectClass( envptr, innerObj ); -//webview // jmethodID mid = env->GetMethodID( envptr, innerClass, "getClass", "()Ljava/lang/Class;"); -//webview // jobject clsObj = env->CallObjectMethod( envptr, innerObj, mid ); -//webview // jclass clazzz = env->GetObjectClass( envptr, clsObj ); -//webview // mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;"); -//webview // strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid); -//webview // name = env->GetStringUTFChars( envptr, strObj, 0); -//webview // env->DeleteLocalRef( envptr, clsObj ); -//webview // env->DeleteLocalRef( envptr, clazzz ); -//webview // } -//webview // -//webview // if( strcmp( name, "z5" ) == 0 ) -//webview // { -//webview // // Special, Some Androids (notably Meta Quest) use a different private message type. -//webview // jfieldID mstrf = env->GetFieldID( envptr, innerClass, "a", "[B" ); -//webview // jbyteArray jba = (jstring)env->GetObjectField(envptr, innerObj, mstrf ); -//webview // int len = env->GetArrayLength( envptr, jba ); -//webview // jboolean isCopy = 0; -//webview // jbyte * bufferPtr = env->GetByteArrayElements(envptr, jba, &isCopy); -//webview // -//webview // if( len >= 6 ) -//webview // { -//webview // const char *descr = (const char*)bufferPtr + 6; -//webview // char tcpy[len-5]; -//webview // memcpy( tcpy, descr, len-6 ); -//webview // tcpy[len-6] = 0; -//webview // snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", tcpy ); -//webview // -//webview // env->DeleteLocalRef( envptr, jba ); -//webview // } -//webview // } -//webview // else -//webview // { -//webview // jobject MessagePayload = env->GetObjectField( envptr, innerObj, pairfirst ); -//webview // // MessagePayload is a org.chromium.content_public.browser.MessagePayload -//webview // -//webview // jclass mpclass = env->GetObjectClass( envptr, MessagePayload ); -//webview // -//webview // // Get field "b" which is the web message payload. -//webview // // If you are using binary sockets, it will be in `c` and be a byte array. -//webview // jfieldID mstrf = env->GetFieldID( envptr, mpclass, "b", "Ljava/lang/String;" ); -//webview // jstring strObjDescr = (jstring)env->GetObjectField(envptr, MessagePayload, mstrf ); -//webview // -//webview // const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0); -//webview // snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", descr ); -//webview // -//webview // env->ReleaseStringUTFChars(envptr, strObjDescr, descr); -//webview // env->DeleteLocalRef( envptr, strObjDescr ); -//webview // env->DeleteLocalRef( envptr, MessagePayload ); -//webview // env->DeleteLocalRef( envptr, mpclass ); -//webview // } -//webview // env->ReleaseStringUTFChars(envptr, strObj, name); -//webview // env->DeleteLocalRef( envptr, strObj ); -//webview // env->DeleteLocalRef( envptr, msg ); -//webview // env->DeleteLocalRef( envptr, innerObj ); -//webview // env->DeleteLocalRef( envptr, innerClass ); -//webview // } -//webview //} -//webview // -//webview //void SetupJSThread() -//webview //{ -//webview // pthread_create( &jsthread, 0, JavscriptThread, 0 ); -//webview //} + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + while( g_attachLooper == 0 ) usleep(1); + if(screenx<1000){ + screenx=1000; + } + if(screeny<1000){ + screeny=1000; + } + //WebViewCreate( wvn, "https://server.alexmahr.de/browser.html"file:///android_asset/index.html", g_attachLooper, screenx, screeny); //500, 500 ); + WebViewCreate( wvn, "https://server.alexmahr.de/browser.html", g_attachLooper, screenx, screeny); //500, 500 ); + //WebViewCreate( wvn, "about:blank", g_attachLooper, 500, 500 ); +} + + +pthread_t jsthread; + +void * JavscriptThread( void * v ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + // Create a looper on this thread... + jclass LooperClass = env->FindClass(envptr, "android/os/Looper"); + jmethodID myLooperMethod = env->GetStaticMethodID(envptr, LooperClass, "myLooper", "()Landroid/os/Looper;"); + jobject thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod ); + if( !thisLooper ) + { + jmethodID prepareMethod = env->GetStaticMethodID(envptr, LooperClass, "prepare", "()V"); + env->CallStaticVoidMethod( envptr, LooperClass, prepareMethod ); + thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod ); + g_attachLooper = env->NewGlobalRef(envptr, thisLooper); + } + + jmethodID getQueueMethod = env->GetMethodID( envptr, LooperClass, "getQueue", "()Landroid/os/MessageQueue;" ); + jobject lque = env->CallObjectMethod( envptr, g_attachLooper, getQueueMethod ); + + jclass MessageQueueClass = env->FindClass(envptr, "android/os/MessageQueue"); + jmethodID nextMethod = env->GetMethodID( envptr, MessageQueueClass, "next", "()Landroid/os/Message;" ); + + jclass MessageClass = env->FindClass(envptr, "android/os/Message"); + jfieldID objid = env->GetFieldID( envptr, MessageClass, "obj", "Ljava/lang/Object;" ); + jclass PairClass = env->FindClass(envptr, "android/util/Pair"); + jfieldID pairfirst = env->GetFieldID( envptr, PairClass, "first", "Ljava/lang/Object;" ); + + while(1) + { + // Instead of using Looper::loop(), we just call next on the looper object. + jobject msg = env->CallObjectMethod( envptr, lque, nextMethod ); + jobject innerObj = env->GetObjectField( envptr, msg, objid ); + const char * name; + jstring strObj; + jclass innerClass; + + // Check Object Type + { + innerClass = env->GetObjectClass( envptr, innerObj ); + jmethodID mid = env->GetMethodID( envptr, innerClass, "getClass", "()Ljava/lang/Class;"); + jobject clsObj = env->CallObjectMethod( envptr, innerObj, mid ); + jclass clazzz = env->GetObjectClass( envptr, clsObj ); + mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;"); + strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid); + name = env->GetStringUTFChars( envptr, strObj, 0); + env->DeleteLocalRef( envptr, clsObj ); + env->DeleteLocalRef( envptr, clazzz ); + } + + if( strcmp( name, "z5" ) == 0 ) + { + // Special, Some Androids (notably Meta Quest) use a different private message type. + jfieldID mstrf = env->GetFieldID( envptr, innerClass, "a", "[B" ); + jbyteArray jba = (jstring)env->GetObjectField(envptr, innerObj, mstrf ); + int len = env->GetArrayLength( envptr, jba ); + jboolean isCopy = 0; + jbyte * bufferPtr = env->GetByteArrayElements(envptr, jba, &isCopy); + + if( len >= 6 ) + { + const char *descr = (const char*)bufferPtr + 6; + char tcpy[len-5]; + memcpy( tcpy, descr, len-6 ); + tcpy[len-6] = 0; + snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", tcpy ); + + env->DeleteLocalRef( envptr, jba ); + } + } + else + { + jobject MessagePayload = env->GetObjectField( envptr, innerObj, pairfirst ); + // MessagePayload is a org.chromium.content_public.browser.MessagePayload + + jclass mpclass = env->GetObjectClass( envptr, MessagePayload ); + + // Get field "b" which is the web message payload. + // If you are using binary sockets, it will be in `c` and be a byte array. + jfieldID mstrf = env->GetFieldID( envptr, mpclass, "b", "Ljava/lang/String;" ); + jstring strObjDescr = (jstring)env->GetObjectField(envptr, MessagePayload, mstrf ); + + const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0); + snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", descr ); + + env->ReleaseStringUTFChars(envptr, strObjDescr, descr); + env->DeleteLocalRef( envptr, strObjDescr ); + env->DeleteLocalRef( envptr, MessagePayload ); + env->DeleteLocalRef( envptr, mpclass ); + } + env->ReleaseStringUTFChars(envptr, strObj, name); + env->DeleteLocalRef( envptr, strObj ); + env->DeleteLocalRef( envptr, msg ); + env->DeleteLocalRef( envptr, innerObj ); + env->DeleteLocalRef( envptr, innerClass ); + } +} + +void SetupJSThread() +{ + pthread_create( &jsthread, 0, JavscriptThread, 0 ); +} int main( int argc, char ** argv ) { //Heightmap int x, y; //why this? double ThisTime; //why this? double LastFPSTime = OGGetAbsoluteTime(); - Log( "Starting Up" ); CNFGBGColor = 0x000040ff; CNFGSetupFullscreen( "Test Bench", 0 ); + CNFGGetDimensions( &screenx, &screeny ); + char mystring[900]; + + size_t webviewdatasize = sizeof(uint32_t) * screeny * screenx; + sprintf( mystring, "MYSTRING %dx%d,%ld",screenx, screeny,webviewdatasize); + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME,"%s",mystring); +// Log(mystring ); + + webviewdata = malloc(webviewdatasize); + HandleWindowTermination = HandleThisWindowTermination; @@ -550,11 +572,11 @@ int main( int argc, char ** argv ) //SOUND // Disabled, for now. //SOUND //InitCNFAAndroid( AudioCallback, "A Name", SAMPLE_RATE, 0, 1, 0, SAMPLE_COUNT, 0, 0, 0 ); -//webview// SetupJSThread(); + SetupJSThread(); -//webview // Create webview and wait for its completion -//webview // RunCallbackOnUIThread( SetupWebView, &MyWebView ); -//webview // while( !MyWebView.WebViewObject ) usleep(1); +// Create webview and wait for its completion + RunCallbackOnUIThread( SetupWebView, &MyWebView ); + while( !MyWebView.WebViewObject ) usleep(1); Log( "Startup Complete" ); @@ -573,8 +595,8 @@ int main( int argc, char ** argv ) if( suspended ) { usleep(1000000); continue; } -//webview // RunCallbackOnUIThread( (void(*)(void*))WebViewRequestRenderToCanvas, &MyWebView ); -//webview // RunCallbackOnUIThread( CheckWebView, &MyWebView ); + RunCallbackOnUIThread( (void(*)(void*))WebViewRequestRenderToCanvas, &MyWebView ); + RunCallbackOnUIThread( CheckWebView, &MyWebView ); CNFGClearFrame(); CNFGColor( 0xFFFFFFFF ); @@ -633,10 +655,10 @@ int main( int argc, char ** argv ) //graphic-triangles CNFGTackPoly( pp, 3 ); //graphic-triangles } -//webview // Last WebMessage -//webview CNFGColor( 0xFFFFFFFF ); -//webview CNFGPenX = 0; CNFGPenY = 100; -//webview // CNFGDrawText( fromJSBuffer, 6 ); + // Last WebMessage + CNFGColor( 0xFFFFFFFF ); + CNFGPenX = 0; CNFGPenY = 100; + CNFGDrawText( fromJSBuffer, 6 ); //graphic-random-texture int x, y; //graphic-random-texture for( y = 0; y < 256; y++ ) @@ -644,8 +666,8 @@ int main( int argc, char ** argv ) //graphic-random-texture randomtexturedata[x+y*256] = x | ((x*394543L+y*355+iframeno*3)<<8); //graphic-random-texture CNFGBlitImage( randomtexturedata, 100, 600, 256, 256 ); -//webview //WebViewNativeGetPixels( &MyWebView, webviewdata, 500, 500 ); -//webview //CNFGBlitImage( webviewdata, 500, 640, 500, 500 ); + WebViewNativeGetPixels( &MyWebView, webviewdata,screenx,screeny); + CNFGBlitImage( webviewdata, 0, 0, screenx,screeny ); //why this? frames++; //On Android, CNFGSwapBuffers must be called, and CNFGUpdateScreenWithBitmap does not have an implied framebuffer swap. diff --git a/app/src/nativecode/webview_native_activity.h b/app/src/nativecode/webview_native_activity.h new file mode 100644 index 0000000..b850540 --- /dev/null +++ b/app/src/nativecode/webview_native_activity.h @@ -0,0 +1,312 @@ +#ifndef _WEBVIEW_NATIVE_ACTIVITY +#define _WEBVIEW_NATIVE_ACTIVITY + +#include + +extern volatile jobject g_objRootView; + +typedef struct +{ + jobject WebViewObject; + jobjectArray MessageChannels; + jobject BackingBitmap; + jobject BackingCanvas; + int updated_canvas; + int w, h; +} WebViewNativeActivityObject; + +// Must be called from main thread + +// initial_url = "about:blank" for a java-script only page. Can also be file:///android_asset/test.html. +// Loading from "about:blank" will make the page ready almost immediately, otherwise it's about 50ms to load. +// useLooperForWebMessages is required, and must be a global jobject of your preferred looper to handle webmessages. +void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph ); +void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js ); + +// Note: Do not initialize until page reports as 100% loaded, with WebViewGetProgress. +void WebViewPostMessage( WebViewNativeActivityObject * obj, const char * mesg, int initial ); +void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj ); +int WebViewGetProgress( WebViewNativeActivityObject * obj ); +char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj ); + +// Can be called from any thread. +void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h ); + +#ifdef WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION + +volatile jobject g_objRootView; + +void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + jobject clazz = gapp->activity->clazz; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + if( g_objRootView == 0 ) + { + jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout"); + jmethodID ViewConstructor = env->GetMethodID(envptr, ViewClass, "", "(Landroid/content/Context;)V"); + jclass activityClass = env->FindClass(envptr, "android/app/Activity"); + jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;"); + jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod); + jobject jv = env->NewObject(envptr, ViewClass, ViewConstructor, contextObject ); + g_objRootView = env->NewGlobalRef(envptr, jv); + + jclass clszz = env->GetObjectClass(envptr,clazz); + jmethodID setContentViewMethod = env->GetMethodID(envptr, clszz, "setContentView", "(Landroid/view/View;)V"); + env->CallVoidMethod(envptr,clazz, setContentViewMethod, g_objRootView ); + } + + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jclass activityClass = env->FindClass(envptr, "android/app/Activity"); + jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;"); + jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod); + + jmethodID WebViewConstructor = env->GetMethodID(envptr, WebViewClass, "", "(Landroid/content/Context;)V"); + jobject wvObj = env->NewObject(envptr, WebViewClass, WebViewConstructor, contextObject ); + + // Unknown reason why - if you don't first load about:blank, it sometimes doesn't render right? + // Even more annoying - you can't pre-use loadUrl if you want to use message channels. + jmethodID WebViewLoadBaseURLMethod = env->GetMethodID(envptr, WebViewClass, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + jstring strul = env->NewStringUTF( envptr, "http://example.com" ); + jstring strdata = env->NewStringUTF( envptr, "not-yet-loaded" ); + jstring strmime = env->NewStringUTF( envptr, "text/html" ); + jstring strencoding = env->NewStringUTF( envptr, "utf8" ); + jstring strhistoryurl = env->NewStringUTF( envptr, "" ); + env->CallVoidMethod(envptr, wvObj, WebViewLoadBaseURLMethod, strul, strdata, strmime, strencoding, strhistoryurl ); + env->DeleteLocalRef( envptr, strul ); + env->DeleteLocalRef( envptr, strdata ); + env->DeleteLocalRef( envptr, strmime ); + env->DeleteLocalRef( envptr, strencoding ); + env->DeleteLocalRef( envptr, strhistoryurl ); + + // You have to switch to this to be able to run javascript code. + jmethodID LoadURLMethod = env->GetMethodID(envptr, WebViewClass, "loadUrl", "(Ljava/lang/String;)V"); + jstring strjs = env->NewStringUTF( envptr, initial_url ); + env->CallVoidMethod(envptr, wvObj, LoadURLMethod, strjs ); + env->DeleteLocalRef( envptr, strjs ); + + jmethodID WebViewGetSettingMethod = env->GetMethodID(envptr, WebViewClass, "getSettings", "()Landroid/webkit/WebSettings;"); + jobject websettings = env->CallObjectMethod(envptr, wvObj, WebViewGetSettingMethod ); + jclass WebSettingsClass = env->FindClass(envptr, "android/webkit/WebSettings"); + jmethodID setJavaScriptEnabledMethod = env->GetMethodID(envptr, WebSettingsClass, "setJavaScriptEnabled", "(Z)V"); + env->CallVoidMethod( envptr, websettings, setJavaScriptEnabledMethod, true ); + env->DeleteLocalRef( envptr, websettings ); + + jmethodID setMeasuredDimensionMethodID = env->GetMethodID(envptr, WebViewClass, "setMeasuredDimension", "(II)V"); + env->CallVoidMethod(envptr, wvObj, setMeasuredDimensionMethodID, pw, ph ); + + jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout"); + jmethodID addViewMethod = env->GetMethodID(envptr, ViewClass, "addView", "(Landroid/view/View;)V"); + env->CallVoidMethod( envptr, g_objRootView, addViewMethod, wvObj ); + + jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" ); + jmethodID createWebMessageChannelMethod = env->GetMethodID(envptr, WebViewClass, "createWebMessageChannel", "()[Landroid/webkit/WebMessagePort;"); + jobjectArray messageChannels = env->CallObjectMethod( envptr, wvObj, createWebMessageChannelMethod ); + jobject mc0 = env->GetObjectArrayElement(envptr, messageChannels, 0); // MC1 is handed over to javascript. + + jclass HandlerClassType = env->FindClass(envptr, "android/os/Handler" ); + jmethodID HandlerObjectConstructor = env->GetMethodID(envptr, HandlerClassType, "", "(Landroid/os/Looper;)V"); + jobject handlerObject = env->NewObject( envptr, HandlerClassType, HandlerObjectConstructor, useLooperForWebMessages ); + handlerObject = env->NewGlobalRef(envptr, handlerObject); + jmethodID setWebMessageCallbackMethod = env->GetMethodID( envptr, WebMessagePortClass, "setWebMessageCallback", "(Landroid/webkit/WebMessagePort$WebMessageCallback;Landroid/os/Handler;)V" ); + + // Only can receive messages on MC0 + env->CallVoidMethod( envptr, mc0, setWebMessageCallbackMethod, 0, handlerObject ); + + // Generate backing bitmap and canvas. + jclass CanvasClass = env->FindClass(envptr, "android/graphics/Canvas"); + jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap"); + jmethodID createBitmap = env->GetStaticMethodID(envptr, BitmapClass, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); + jclass bmpCfgCls = env->FindClass(envptr, "android/graphics/Bitmap$Config"); + jstring bitmap_mode = env->NewStringUTF(envptr, "ARGB_8888"); + jmethodID bmpClsValueOfMid = env->GetStaticMethodID(envptr, bmpCfgCls, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); + jobject jBmpCfg = env->CallStaticObjectMethod(envptr, bmpCfgCls, bmpClsValueOfMid, bitmap_mode); + jobject bitmap = env->CallStaticObjectMethod( envptr, BitmapClass, createBitmap, pw, ph, jBmpCfg ); + jmethodID canvasConstructor = env->GetMethodID(envptr, CanvasClass, "", "(Landroid/graphics/Bitmap;)V"); + jobject canvas = env->NewObject(envptr, CanvasClass, canvasConstructor, bitmap ); + + env->DeleteLocalRef( envptr, CanvasClass ); + env->DeleteLocalRef( envptr, BitmapClass ); + env->DeleteLocalRef( envptr, bmpCfgCls ); + env->DeleteLocalRef( envptr, bitmap_mode ); + + w->BackingBitmap = env->NewGlobalRef(envptr, bitmap ); + w->BackingCanvas = env->NewGlobalRef(envptr, canvas ); + w->WebViewObject = env->NewGlobalRef(envptr, wvObj); + w->MessageChannels = env->NewGlobalRef(envptr, messageChannels); + w->w = pw; + w->h = ph; + + env->DeleteLocalRef( envptr, WebViewClass ); + env->DeleteLocalRef( envptr, activityClass ); + env->DeleteLocalRef( envptr, WebSettingsClass ); + env->DeleteLocalRef( envptr, ViewClass ); +} + +int WebViewGetProgress( WebViewNativeActivityObject * obj ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jmethodID WebViewProgress = env->GetMethodID(envptr, WebViewClass, "getProgress", "()I"); + int ret = env->CallIntMethod( envptr, obj->WebViewObject, WebViewProgress ); + env->DeleteLocalRef( envptr, WebViewClass ); + return ret; +} + +void WebViewPostMessage( WebViewNativeActivityObject * w, const char * mesg, int initial ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" ); + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jclass WebMessageClass = env->FindClass(envptr, "android/webkit/WebMessage" ); + + jstring strjs = env->NewStringUTF( envptr, mesg ); + + if( initial ) + { + jobject mc1 = env->GetObjectArrayElement(envptr, w->MessageChannels, 1); + jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "", "(Ljava/lang/String;[Landroid/webkit/WebMessagePort;)V"); + + //https://stackoverflow.com/questions/41753104/how-do-you-use-webmessageport-as-an-alternative-to-addjavascriptinterface + // Only on initial hop do we want to post the root webmessage, which hooks up out webmessage port. + jmethodID postMessageMethod = env->GetMethodID(envptr, WebViewClass, "postWebMessage", "(Landroid/webkit/WebMessage;Landroid/net/Uri;)V"); + + // Need to generate a new message channel array. + jobjectArray jsUseWebPorts = env->NewObjectArray( envptr, 1, WebMessagePortClass, mc1); + + // Need Uri.EMPTY + jclass UriClass = env->FindClass(envptr, "android/net/Uri" ); + jfieldID EmptyField = env->GetStaticFieldID( envptr, UriClass, "EMPTY", "Landroid/net/Uri;" ); + jobject EmptyURI = env->GetStaticObjectField( envptr, UriClass, EmptyField ); + + + jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs, jsUseWebPorts ); + env->CallVoidMethod( envptr, w->WebViewObject, postMessageMethod, newwm, EmptyURI ); + + env->DeleteLocalRef( envptr, jsUseWebPorts ); + env->DeleteLocalRef( envptr, newwm ); + env->DeleteLocalRef( envptr, EmptyURI ); + env->DeleteLocalRef( envptr, UriClass ); + } + else + { + jobject mc0 = env->GetObjectArrayElement(envptr, w->MessageChannels, 0); + jmethodID postMessageMethod = env->GetMethodID(envptr, WebMessagePortClass, "postMessage", "(Landroid/webkit/WebMessage;)V"); + jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "", "(Ljava/lang/String;)V"); + + jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs ); + env->CallVoidMethod( envptr, mc0, postMessageMethod, newwm ); + + env->DeleteLocalRef( envptr, newwm ); + env->DeleteLocalRef( envptr, mc0 ); + } + + env->DeleteLocalRef( envptr, strjs ); + env->DeleteLocalRef( envptr, WebViewClass ); + env->DeleteLocalRef( envptr, WebMessageClass ); + env->DeleteLocalRef( envptr, WebMessagePortClass ); +} + +void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jmethodID drawMethod = env->GetMethodID(envptr, WebViewClass, "draw", "(Landroid/graphics/Canvas;)V"); + env->CallVoidMethod( envptr, obj->WebViewObject, drawMethod, obj->BackingCanvas ); + env->DeleteLocalRef( envptr, WebViewClass ); +} + +void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap"); + jobject buffer = env->NewDirectByteBuffer(envptr, pixel_data, obj->w*obj->h*4 ); + jmethodID copyPixelsBufferID = env->GetMethodID( envptr, BitmapClass, "copyPixelsToBuffer", "(Ljava/nio/Buffer;)V" ); + env->CallVoidMethod( envptr, obj->BackingBitmap, copyPixelsBufferID, buffer ); + + int i; + int num = obj->w * obj->h; + for( i = 0; i < num; i++ ) pixel_data[i] = bswap_32( pixel_data[i] ); + + env->DeleteLocalRef( envptr, BitmapClass ); + env->DeleteLocalRef( envptr, buffer ); + + jnii->DetachCurrentThread( jniiptr ); +} + +void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jmethodID WebViewEvalJSMethod = env->GetMethodID(envptr, WebViewClass, "evaluateJavascript", "(Ljava/lang/String;Landroid/webkit/ValueCallback;)V"); + + //WebView.evaluateJavascript(String script, ValueCallback resultCallback) + jstring strjs = env->NewStringUTF( envptr, js ); + env->CallVoidMethod( envptr, obj->WebViewObject, WebViewEvalJSMethod, strjs, 0 ); // Tricky: resultCallback = 0, if you try running looper.loop() it will crash - only manually process messages. + env->DeleteLocalRef( envptr, WebViewClass ); + env->DeleteLocalRef( envptr, strjs ); +} + +char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj ) +{ + const struct JNINativeInterface * env = 0; + const struct JNINativeInterface ** envptr = &env; + const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; + const struct JNIInvokeInterface * jnii = *jniiptr; + jnii->AttachCurrentThread( jniiptr, &envptr, NULL); + env = (*envptr); + + jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); + jmethodID getTitle = env->GetMethodID(envptr, WebViewClass, "getTitle", "()Ljava/lang/String;"); + jobject titleObject = env->CallObjectMethod( envptr, obj->WebViewObject, getTitle ); + char *nativeString = strdup( env->GetStringUTFChars(envptr, titleObject, 0) ); + env->DeleteLocalRef( envptr, titleObject ); + env->DeleteLocalRef( envptr, WebViewClass ); + + return nativeString; +} + + +#endif +#endif +