-
Notifications
You must be signed in to change notification settings - Fork 228
/
webview_native_activity.h
312 lines (250 loc) · 15.1 KB
/
webview_native_activity.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#ifndef _WEBVIEW_NATIVE_ACTIVITY
#define _WEBVIEW_NATIVE_ACTIVITY
#include <android/native_activity.h>
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, "<init>", "(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, "<init>", "(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, "<init>", "(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, "<init>", "(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, "<init>", "(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, "<init>", "(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<String> 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