1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "engine.h"
17 #include <android/input.h>
18 #include <jni.h>
19 #include <stdio.h>
20 #include <algorithm>
21 #include <iterator>
22 #include <memory>
23 #include <string>
24 #include <utility>
25 #include <vector>
26 #include "logdefs.h"
27
28 Engine* applicationEngine = nullptr;
29
type_event_cb(Engine * engine,void * ctx)30 static void type_event_cb(Engine* engine, void* ctx) {
31 InputTextEventState* state = (InputTextEventState*)ctx;
32 engine->peer->callOnTextInput(state->type, state->cursorPosition, state->characters);
33 delete state;
34 }
35
key_event_cb(Engine * engine,void * ctx)36 static void key_event_cb(Engine* engine, void* ctx) {
37 InputKeyEventState* state = (InputKeyEventState*)ctx;
38 engine->peer->callOnKey(state->key, state->modifiers, state->action);
39 delete state;
40 }
41
permissions_event_cb(Engine * engine,void * ctx)42 static void permissions_event_cb(Engine* engine, void* ctx) {
43 PermissionGrantedEventState* state = (PermissionGrantedEventState*)ctx;
44 engine->peer->callPermissionGranted(state->requestCode, state->permissions, state->grantResults);
45 delete state;
46 }
47
48
callOnUIThread(engine_callback_t cb,void * state)49 void Engine::callOnUIThread(engine_callback_t cb, void* state) {
50 locker.lock();
51 cb_queue.push_back(std::make_pair(cb, state));
52 locker.unlock();
53 }
54
processQueue()55 void Engine::processQueue() {
56 locker.lock();
57 while (cb_queue.size() > 0) {
58 auto element = cb_queue.front();
59 cb_queue.pop_front();
60 locker.unlock();
61 element.first(this, element.second);
62 locker.lock();
63 }
64 locker.unlock();
65 }
66
toStdString(JNIEnv * env,jstring jStr)67 std::string toStdString(JNIEnv *env, jstring jStr) {
68 if (!jStr)
69 return "";
70
71 const jclass stringClass = env->GetObjectClass(jStr);
72 const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
73 const jbyteArray bytes = (jbyteArray) env->CallObjectMethod(jStr, getBytes, env->NewStringUTF("UTF-8"));
74
75 size_t length = (size_t) env->GetArrayLength(bytes);
76 jbyte* pBytes = env->GetByteArrayElements(bytes, NULL);
77
78 std::string ret = std::string((char *)pBytes, length);
79 env->ReleaseByteArrayElements(bytes, pBytes, JNI_ABORT);
80
81 env->DeleteLocalRef(bytes);
82 env->DeleteLocalRef(stringClass);
83 return ret;
84 }
85
Java_ohos_koala_InputProxy_sendType(JNIEnv * env,jobject obj,jint type,jint cursor_start,jstring characters)86 extern "C" JNIEXPORT void JNICALL Java_ohos_koala_InputProxy_sendType(
87 JNIEnv* env, jobject obj, jint type, jint cursor_start, jstring characters) {
88 auto* engine = applicationEngine;
89 if (engine != nullptr) {
90 engine->callOnUIThread(
91 type_event_cb,
92 new InputTextEventState(type, cursor_start, toStdString(env, characters))
93 );
94 }
95 }
96
Java_ohos_koala_InputProxy_sendKey(JNIEnv * env,jobject obj,jint key,jint modifiers,jint action)97 extern "C" JNIEXPORT void JNICALL Java_ohos_koala_InputProxy_sendKey(
98 JNIEnv* env, jobject obj, jint key, jint modifiers, jint action) {
99 auto* engine = applicationEngine;
100 if (engine != nullptr) {
101 int type = (action == AKEY_EVENT_ACTION_UP) ? 0 : 1;
102 engine->callOnUIThread(key_event_cb,new InputKeyEventState(key, modifiers, type));
103 }
104 }
105
Java_ohos_koala_RequestHandler_sendPermissionsInfo(JNIEnv * env,jobject obj,jint requestCode,jobjectArray permissions,jintArray grantResults)106 extern "C" JNIEXPORT void JNICALL Java_ohos_koala_RequestHandler_sendPermissionsInfo(
107 JNIEnv* env, jobject obj, jint requestCode, jobjectArray permissions, jintArray grantResults) {
108 auto* engine = applicationEngine;
109 if (engine != nullptr) {
110 jint arrayLength = env->GetArrayLength(permissions);
111 auto results = env->GetIntArrayElements(grantResults, JNI_FALSE);
112
113 std::vector<std::string> perms(arrayLength);
114 std::vector<int> vgrantResults(arrayLength);
115
116 for (auto i=0; i<arrayLength; i++) {
117 jstring obj = (jstring) env->GetObjectArrayElement(permissions, i);
118 perms[i] = env->GetStringUTFChars(obj, JNI_FALSE);
119 vgrantResults[i] = results[i];
120 env->DeleteLocalRef(obj);
121 }
122
123 engine->callOnUIThread(permissions_event_cb,new PermissionGrantedEventState(requestCode, perms, vgrantResults));
124 }
125 }
126
Engine(android_app * app)127 Engine::Engine(android_app *app) {
128 this->app = app;
129 androidCpuFamily = android_getCpuFamily();
130 canDraw = 0;
131 display = nullptr;
132 surface = nullptr;
133 context = nullptr;
134 density = 0.0f;
135 peer = nullptr;
136 savedState.index = 0;
137 vibrationEffectClass = nullptr;
138 vibratorInstance = nullptr;
139 vibrateWithDurationMethod = nullptr;
140 vibrateWithEffectMethod = nullptr;
141 createPredefinedEffectMethod = nullptr;
142 applicationEngine = this;
143 }
144
softKeyboard(bool show)145 void Engine::softKeyboard(bool show) {
146 if (!checkJniBridge()) {
147 return;
148 }
149
150 auto* nativeActivity = jniBridge->getObjectClass(app->activity->clazz);
151 auto* viewKeyboardMethod = jniBridge->getMethodID(nativeActivity, "keyboard", "(Z)V");
152 jniBridge->callVoidMethod(app->activity->clazz, viewKeyboardMethod, show);
153 }
154
globalPointer(int32_t & x,int32_t & y)155 void Engine::globalPointer(int32_t &x, int32_t &y) {
156 if (!checkJniBridge()) {
157 return;
158 }
159 auto* nativeActivity = jniBridge->getObjectClass(app->activity->clazz);
160 auto* method = jniBridge->getMethodID(nativeActivity, "getMousePointer", "()[F");
161 jfloatArray javaArray = (jfloatArray)jniBridge->callObjectMethod(app->activity->clazz, method);
162 jfloat* array = jniBridge->getFloatArrayElement(javaArray);
163 x = (int)array[0];
164 y = (int)array[1];
165 }
166
commitInput()167 void Engine::commitInput() {
168 if (!checkJniBridge()) {
169 return;
170 }
171
172 auto* nativeActivity = jniBridge->getObjectClass(app->activity->clazz);
173 auto* commitInputMethod = jniBridge->getMethodID(nativeActivity, "commitInput", "()V");
174 jniBridge->callVoidMethod(app->activity->clazz, commitInputMethod);
175 }
176
androidPermissionName(const char * perm_name)177 jstring Engine::androidPermissionName(const char* perm_name) {
178 jclass manifestPermissionClass = jniBridge->findClass(
179 "android/Manifest$permission"
180 );
181 jfieldID actualPermission = jniBridge->getStaticFieldID(
182 manifestPermissionClass, perm_name, "Ljava/lang/String;"
183 );
184
185 jstring actualPermissionString = (jstring)(jniBridge->getStaticObjectField(
186 manifestPermissionClass, actualPermission
187 ));
188 return actualPermissionString;
189 }
190
androidExistPermission(const char * perm_name)191 bool Engine::androidExistPermission(const char *perm_name) {
192 jclass manifestPermissionClass = jniBridge->findClass(
193 "android/Manifest$permission"
194 );
195 jfieldID actualPermission = jniBridge->getStaticFieldID(
196 manifestPermissionClass, perm_name, "Ljava/lang/String;"
197 );
198
199 if (!actualPermission) {
200 jniBridge->check();
201 return false;
202 }
203
204 return true;
205 }
206
androidHasPermission(const char * perm_name)207 bool Engine::androidHasPermission(const char* perm_name) {
208 bool result = false;
209
210 jstring actualPermissionString = androidPermissionName(perm_name);
211
212 jint PERMISSION_GRANTED = -1;
213 jclass packageManagerClass = jniBridge->findClass(
214 "android/content/pm/PackageManager"
215 );
216 jfieldID permissionGrantedField = jniBridge->getStaticFieldID(
217 packageManagerClass, "PERMISSION_GRANTED", "I"
218 );
219 PERMISSION_GRANTED = jniBridge->getStaticIntField(
220 packageManagerClass, permissionGrantedField
221 );
222 jobject activity = app->activity->clazz;
223 jclass contextClass = jniBridge->findClass(
224 "android/content/Context"
225 );
226 jmethodID checkSelfPermissionMethod = jniBridge->getMethodID(
227 contextClass, "checkSelfPermission", "(Ljava/lang/String;)I"
228 );
229 jint intResult = jniBridge->callIntMethod(
230 activity, checkSelfPermissionMethod, actualPermissionString
231 );
232 result = (intResult == PERMISSION_GRANTED);
233
234 return result;
235 }
236
askPermissions(std::vector<std::string> & perms)237 void Engine::askPermissions(std::vector<std::string> &perms) {
238 if (!checkJniBridge()) return;
239
240 jclass stringClass = jniBridge->findClass("java/lang/String");
241 jobjectArray permArray = jniBridge->makeArray(
242 stringClass,
243 perms.size(),
244 jniBridge->makeString("")
245 );
246
247 for (int i = 0; i < perms.size(); i++) {
248 jobject permString = androidPermissionName(perms[i].c_str());
249 jniBridge->setArrayElement(
250 permArray, i,
251 permString
252 );
253 }
254
255 jobject activity = app->activity->clazz;
256 jclass classActivity = jniBridge->getObjectClass(app->activity->clazz);
257 jmethodID requestPermissionsMethod = jniBridge->getMethodID(
258 classActivity, "requestPermissions", "([Ljava/lang/String;I)V"
259 );
260
261 jniBridge->callVoidMethod(
262 activity, requestPermissionsMethod, permArray, 0
263 );
264 }
265
initNode()266 bool Engine::initNode() {
267 if (androidCpuFamily != ANDROID_CPU_FAMILY_ARM64 && androidCpuFamily != ANDROID_CPU_FAMILY_X86_64) {
268 LOGE("Unsupported CPU, cannot run node.js!");
269 return false;
270 }
271 nodeRuntime = std::make_unique<NodeRuntime>();
272 #ifdef KOALA_USE_PROFILE
273 bool inspect = true;
274 #else
275 bool inspect = false;
276 #endif
277 if (!nodeRuntime->init(inspect)) {
278 nodeRuntime.reset();
279 LOGE("Cannot initialize node.js runtime");
280 return false;
281 }
282 nodeScopedState = std::make_unique<NodeScopedState>(nodeRuntime->setup);
283 return true;
284 }
285
deinitNode()286 void Engine::deinitNode() {
287 canDraw = 0;
288 nodeScopedState.reset();
289 }
290
runNode()291 void Engine::runNode() {
292 if (!canDraw || !nodeRuntime) return;
293 uv_run(nodeRuntime->setup->event_loop(), UV_RUN_NOWAIT);
294 nodeRuntime->platform->DrainTasks(nodeScopedState->isolate);
295 }
296
initDisplay()297 int Engine::initDisplay() {
298 const EGLint attributes[] = {
299 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
300 EGL_BLUE_SIZE, 8,
301 EGL_GREEN_SIZE, 8,
302 EGL_RED_SIZE, 8,
303 EGL_ALPHA_SIZE, 8,
304 EGL_CONFORMANT, EGL_OPENGL_ES2_BIT,
305 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
306 EGL_NONE
307 };
308 EGLint format;
309 EGLint numConfigs;
310 EGLConfig config = nullptr;
311 EGLSurface surface;
312 EGLContext context;
313
314 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
315 eglInitialize(display, nullptr, nullptr);
316
317 /* Here, the application chooses the configuration it desires.
318 * find the best match if possible, otherwise use the very first one
319 */
320 eglChooseConfig(display, attributes, nullptr, 0, &numConfigs);
321 std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
322 assert(supportedConfigs);
323 eglChooseConfig(display, attributes, supportedConfigs.get(), numConfigs, &numConfigs);
324 assert(numConfigs);
325 auto i = 0;
326 for (; i < numConfigs; i++) {
327 auto& cfg = supportedConfigs[i];
328 EGLint r = 0, g = 0, b = 0, a = 0;
329 if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) &&
330 eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
331 eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) &&
332 eglGetConfigAttrib(display, cfg, EGL_ALPHA_SIZE, &a)) {
333 if (r == 8 && g == 8 && b == 8 && a == 8) {
334 config = supportedConfigs[i];
335 break;
336 }
337 }
338 }
339 if (i == numConfigs) {
340 config = supportedConfigs[0];
341 }
342
343 if (config == nullptr) {
344 LOGW_ANDROID("Unable to initialize EGLConfig");
345 return -1;
346 }
347
348 /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
349 * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
350 * As soon as we picked a EGLConfig, we can safely reconfigure the
351 * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
352 eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
353
354 surface = eglCreateWindowSurface(display, config, app->window, nullptr);
355 EGLint contextAttribs[] = {
356 EGL_CONTEXT_CLIENT_VERSION, 2,
357 EGL_NONE
358 };
359 context = eglCreateContext(display, config, nullptr, contextAttribs);
360
361 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
362 LOGW_ANDROID("Unable to eglMakeCurrent");
363 return -1;
364 }
365
366 physicalWidth = 0;
367 physicalHeight = 0;
368 eglQuerySurface(display, surface, EGL_WIDTH, &physicalWidth);
369 eglQuerySurface(display, surface, EGL_HEIGHT, &physicalHeight);
370
371 this->display = display;
372 this->context = context;
373 this->surface = surface;
374 this->savedState.index = 0;
375
376 // Check openGL on the system
377 auto opengl_info = { GL_VENDOR, GL_RENDERER, GL_VERSION, GL_EXTENSIONS };
378 for (auto name : opengl_info) {
379 auto info = glGetString(name);
380 LOGI_ANDROID("OpenGL Info: %s", info);
381 }
382 canDraw = 1;
383
384 LOGE("Engine::initDisplay with %d x %d", physicalWidth, physicalHeight);
385
386 return 0;
387 }
388
deinitDisplay()389 void Engine::deinitDisplay() {
390 if (display != EGL_NO_DISPLAY) {
391 eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
392 if (context != EGL_NO_CONTEXT) {
393 eglDestroyContext(display, context);
394 }
395 if (surface != EGL_NO_SURFACE) {
396 eglDestroySurface(display, surface);
397 }
398 eglTerminate(display);
399 }
400 display = EGL_NO_DISPLAY;
401 context = EGL_NO_CONTEXT;
402 surface = EGL_NO_SURFACE;
403
404 canDraw = 0;
405 }
406
resizeDisplay()407 void Engine::resizeDisplay() {
408 if (app->window) {
409 physicalWidth = ANativeWindow_getWidth(app->window);
410 physicalHeight = ANativeWindow_getHeight(app->window);
411 int frameWidth = getFrameWidth();
412 int frameHeight = getFrameHeight();
413 LOGE("Engine::resizeDisplay %p %p %d x %d thiz=%p", peer, app->window, physicalWidth, physicalHeight, this);
414 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::RESIZE, frameWidth, frameHeight));
415 }
416 }
417
handleInput(AInputEvent * event)418 int Engine::handleInput(AInputEvent *event) {
419 auto type = AInputEvent_getType(event);
420 int result = 0;
421
422 switch (type) {
423 case AINPUT_EVENT_TYPE_KEY: {
424 if (peer && canDraw) {
425 int action = AKeyEvent_getAction(event);
426 int key = AKeyEvent_getKeyCode(event);
427 int metaState = AKeyEvent_getMetaState(event);
428 int character = getUnicodeChar(event);
429 LOGI_ANDROID(
430 "Key event: action=%d keyCode=%d metaState=0x%x text:%d",
431 action,
432 key,
433 metaState,
434 character
435 );
436 switch(action) {
437 case AKEY_EVENT_ACTION_MULTIPLE: {
438 // TODO: some how we need to get characters from multi-staged keyboards
439 break;
440 }
441 case AKEY_EVENT_ACTION_DOWN: {
442 if (key == AKEYCODE_BACK) result = 1;
443 // 1 - key down event
444 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::KEY, key, metaState, 1));
445 break;
446 }
447 case AKEY_EVENT_ACTION_UP: {
448 // 0 - key up event
449 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::KEY, key, metaState, 0));
450 break;
451 }
452 }
453 }
454 break;
455 }
456
457 case AINPUT_EVENT_TYPE_MOTION: {
458 int action = getCurrentAction(event);
459 int count = getPointersCount(event);
460 int pointer = getCurrentPointerIndex(event);
461
462 if (peer && canDraw) {
463 auto pointerEvent = std::make_unique<Event>(Kind::TAP, count);
464 pointerEvent->target = pointer;
465
466 for (int i = 0; i < count; i++) {
467 pointerEvent->args1[i] = AMotionEvent_getX(event, i);
468 pointerEvent->args2[i] = AMotionEvent_getY(event, i);
469 }
470
471 maybeScaleVector(pointerEvent.get());
472
473 switch(action) {
474 case AMOTION_EVENT_ACTION_POINTER_DOWN:
475 case AMOTION_EVENT_ACTION_DOWN: {
476 pointerEvent->arg3 = 1;
477 _pendingEvents.push_back(std::forward<std::unique_ptr<Event>>(pointerEvent));
478 break;
479 }
480 case AMOTION_EVENT_ACTION_POINTER_UP:
481 case AMOTION_EVENT_ACTION_UP: {
482 pointerEvent->arg3 = 0;
483 _pendingEvents.push_back(std::forward<std::unique_ptr<Event>>(pointerEvent));
484 break;
485 }
486 case AMOTION_EVENT_ACTION_MOVE: {
487 // TODO: gesture detection.
488 pointerEvent->kind = Kind::MOVE;
489 _pendingEvents.push_back(std::forward<std::unique_ptr<Event>>(pointerEvent));
490 break;
491 }
492 }
493 }
494 break;
495 }
496
497 case AINPUT_EVENT_TYPE_FOCUS:
498 default:
499 break;
500 }
501
502 return result;
503 }
504
505
getUnicodeChar(AInputEvent * event)506 int Engine::getUnicodeChar(AInputEvent *event) {
507 if (!checkJniBridge()) {
508 return 0;
509 }
510 int unicodeKey;
511 auto* class_key_event = jniBridge->findClass("android/view/KeyEvent");
512 auto* eventConstructor = jniBridge->getMethodID(class_key_event, "<init>", "(JJIIIIIIII)V");
513 auto* method_get_unicode_char = jniBridge->getMethodID(class_key_event, "getUnicodeChar", "(I)I");
514 auto* eventObj = jniBridge->newObject(
515 class_key_event,
516 eventConstructor,
517 AKeyEvent_getDownTime(event),
518 AKeyEvent_getEventTime(event),
519 AKeyEvent_getAction(event),
520 AKeyEvent_getKeyCode(event),
521 AKeyEvent_getRepeatCount(event),
522 AKeyEvent_getMetaState(event),
523 AInputEvent_getDeviceId(event),
524 AKeyEvent_getScanCode(event),
525 AKeyEvent_getFlags(event),
526 AInputEvent_getSource(event)
527 );
528
529 unicodeKey = jniBridge->callIntMethod(eventObj, method_get_unicode_char, AKeyEvent_getMetaState(event));
530
531 return unicodeKey;
532 }
533
handleCommand(int command)534 void Engine::handleCommand(int command)
535 {
536 switch (command)
537 {
538 case APP_CMD_INPUT_CHANGED:
539 LOGE("APP_CMD_INPUT_CHANGED");
540 break;
541 case APP_CMD_INIT_WINDOW:
542 LOGE("APP_CMD_INIT_WINDOW: %p", app->window);
543 if (app->window) {
544 initDisplay();
545 }
546 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::INIT));
547 break;
548 case APP_CMD_TERM_WINDOW:
549 LOGE("APP_CMD_TERM_WINDOW");
550 deinitDisplay();
551 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::DEINIT));
552 break;
553 case APP_CMD_GAINED_FOCUS:
554 LOGE("APP_CMD_GAINED_FOCUS");
555 canDraw = 1;
556 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::FOCUS));
557 break;
558 case APP_CMD_LOST_FOCUS:
559 LOGE("APP_CMD_LOST_FOCUS");
560 canDraw = 0;
561 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::UNFOCUS));
562 break;
563 case APP_CMD_CONTENT_RECT_CHANGED:
564 LOGE("APP_CMD_CONTENT_RECT_CHANGED");
565 resizeDisplay();
566 break;
567 case APP_CMD_CONFIG_CHANGED:
568 LOGE("APP_CMD_CONFIG_CHANGED");
569 resizeDisplay();
570 break;
571 case APP_CMD_WINDOW_REDRAW_NEEDED:
572 LOGE("APP_CMD_WINDOW_REDRAW_NEEDED");
573 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::REDRAW));
574 break;
575 case APP_CMD_WINDOW_RESIZED:
576 LOGE("APP_CMD_WINDOW_RESIZED");
577 resizeDisplay();
578 break;
579 case APP_CMD_START:
580 LOGE("APP_CMD_START");
581 break;
582 case APP_CMD_RESUME:
583 LOGE("APP_CMD_RESUME");
584 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::RESUME));
585 break;
586 case APP_CMD_SAVE_STATE:
587 LOGE("APP_CMD_SAVE_STATE");
588 break;
589 case APP_CMD_PAUSE:
590 LOGE("APP_CMD_PAUSE");
591 _pendingEvents.emplace_back(std::make_unique<Event>(Kind::PAUSE));
592 break;
593 case APP_CMD_STOP:
594 LOGE("APP_CMD_STOP");
595 break;
596 case APP_CMD_DESTROY:
597 LOGE("APP_CMD_DESTROY");
598 break;
599 default:
600 LOGE("Unknown: %d", command);
601 break;
602 }
603 }
604
makeCurrent(bool attach)605 bool Engine::makeCurrent(bool attach) {
606 if (attach) {
607 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
608 LOGW_ANDROID("Unable to eglMakeCurrent");
609 return false;
610 }
611 } else {
612 eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
613 }
614 return true;
615 }
616
swapBuffers()617 void Engine::swapBuffers() {
618 if (display && surface != EGL_NO_SURFACE)
619 eglSwapBuffers(display, surface);
620 }
621
checkEvents()622 bool Engine::checkEvents() {
623 processQueue();
624 if (peer) {
625 while (!_pendingEvents.empty()) {
626 auto e = std::move(_pendingEvents.front());
627 _pendingEvents.pop_front();
628 switch (e->kind) {
629 case Kind::RESIZE:
630 peer->callOnResize(e->args1[0], e->args2[0]);
631 break;
632 case Kind::INIT:
633 peer->callOnInit();
634 break;
635 case Kind::DEINIT:
636 peer->callOnDeinit();
637 break;
638 case Kind::FOCUS:
639 peer->callOnFrameEvent(FrameEventType::Focus);
640 break;
641 case Kind::UNFOCUS:
642 peer->callOnFrameEvent(FrameEventType::Unfocus);
643 break;
644 case Kind::REDRAW:
645 peer->callRequestRedraw();
646 break;
647 case Kind::TAP:
648 peer->callOnTap(e->count, e->args1, e->args2, e->arg3, e->target, 0);
649 break;
650 case Kind::MOVE:
651 peer->callOnMove(e->count, e->args1, e->args2);
652 break;
653 case Kind::KEY:
654 peer->callOnKey(e->args1[0], e->args2[0], e->arg3);
655 break;
656 default:
657 // TODO: add lifecycle events.
658 break;
659 }
660 }
661 }
662
663 return app->destroyRequested == 0;
664 }
665
getContentScale()666 float Engine::getContentScale() {
667 if (density == 0.0f) {
668 density = AConfiguration_getDensity(app->config) * (1.0f / (float)ACONFIGURATION_DENSITY_MEDIUM);
669 if (!density) {
670 density = 1.0f / (float)ACONFIGURATION_DENSITY_MEDIUM;
671 }
672 }
673 return density;
674 }
675
getFrame(KNativePointer peerPtr,int width,int height)676 KNativePointer Engine::getFrame(KNativePointer peerPtr, int width, int height) {
677 return peerPtr;
678 }
679
getFrameWidth()680 int32_t Engine::getFrameWidth() {
681 return ANativeWindow_getWidth(app->window) / getContentScale();
682 }
683
getFrameHeight()684 int32_t Engine::getFrameHeight() {
685 return ANativeWindow_getHeight(app->window) / getContentScale();
686 }
687
getPhysicalWidth()688 int32_t Engine::getPhysicalWidth() {
689 return physicalWidth;
690 }
691
getPhysicalHeight()692 int32_t Engine::getPhysicalHeight() {
693 return physicalHeight;
694 }
695
696 class JNIBridgeWrapper {
697 public:
JNIBridgeWrapper(std::shared_ptr<JNIBridge> bridge)698 JNIBridgeWrapper(std::shared_ptr<JNIBridge> bridge): jniBridge(bridge) {}
699
getSystemService(jobject context,const char * service)700 jobject getSystemService(jobject context, const char* service) {
701 auto contextClass = jniBridge->findClass("android/content/Context");
702 auto getSystemServiceMethod = jniBridge->getMethodID(
703 contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
704 return jniBridge->callObjectMethod(context, getSystemServiceMethod, jniBridge->makeString(service));
705 }
706
707 protected:
708 const std::shared_ptr<JNIBridge> jniBridge;
709 };
710
711 class JNIBridgeClipboardPaste : public JNIBridgeWrapper {
712 public:
713 using JNIBridgeWrapper::JNIBridgeWrapper;
714
hasPrimaryClip(jobject clipboard)715 bool hasPrimaryClip(jobject clipboard) {
716 if (!clipboardManagerClass) {
717 clipboardManagerClass = jniBridge->findClass("android/content/ClipboardManager");
718 }
719 auto clipboardManager_hasPrimaryClip = jniBridge->getMethodID(
720 clipboardManagerClass, "hasPrimaryClip", "()Z");
721 return jniBridge->callBooleanMethod(clipboard, clipboardManager_hasPrimaryClip) != JNI_FALSE;
722 }
723
getPrimaryClipDescription(jobject clipboard)724 jobject getPrimaryClipDescription(jobject clipboard) {
725 if (!clipboardManagerClass) {
726 clipboardManagerClass = jniBridge->findClass("android/content/ClipboardManager");
727 }
728 auto clipboardManager_getPrimaryClipDescription = jniBridge->getMethodID(
729 clipboardManagerClass, "getPrimaryClipDescription", "()Landroid/content/ClipDescription;");
730 return jniBridge->callObjectMethod(clipboard, clipboardManager_getPrimaryClipDescription);
731 }
732
hasMimeType(jobject clipDescription,const char * mimeType)733 bool hasMimeType(jobject clipDescription, const char* mimeType) {
734 auto clipDescriptionClass = jniBridge->findClass("android/content/ClipDescription");
735 auto clipDescription_hasMimeType = jniBridge->getMethodID(
736 clipDescriptionClass, "hasMimeType", "(Ljava/lang/String;)Z");
737 return jniBridge->callBooleanMethod(
738 clipDescription, clipDescription_hasMimeType, jniBridge->makeString(mimeType)) != JNI_FALSE;
739 }
740
getPrimaryClip(jobject clipboard)741 jobject getPrimaryClip(jobject clipboard) {
742 if (!clipboardManagerClass) {
743 clipboardManagerClass = jniBridge->findClass("android/content/ClipboardManager");
744 }
745 auto clipboardManager_getPrimaryClip = jniBridge->getMethodID(
746 clipboardManagerClass, "getPrimaryClip", "()Landroid/content/ClipData;");
747 return jniBridge->callObjectMethod(clipboard, clipboardManager_getPrimaryClip);
748 }
749
getClipDataItemAt(jobject clipData,int index)750 jobject getClipDataItemAt(jobject clipData, int index) {
751 auto clipDataClass = jniBridge->findClass("android/content/ClipData");
752 auto clipData_getItemAt = jniBridge->getMethodID(
753 clipDataClass, "getItemAt", "(I)Landroid/content/ClipData$Item;");
754 jvalue jIndex;
755 jIndex.i = index;
756 return jniBridge->callObjectMethod(clipData, clipData_getItemAt, jIndex);
757 }
758
getClipDataItemText(jobject item)759 jstring getClipDataItemText(jobject item) {
760 auto clipDataItemClass = jniBridge->findClass("android/content/ClipData$Item");
761 auto item_getText = jniBridge->getMethodID(
762 clipDataItemClass, "getText", "()Ljava/lang/CharSequence;");
763 auto charSequence = jniBridge->callObjectMethod(item, item_getText);
764 auto charSequenceClass = jniBridge->findClass("java/lang/CharSequence");
765 auto charSequence_toString = jniBridge->getMethodID(
766 charSequenceClass, "toString", "()Ljava/lang/String;");
767 return reinterpret_cast<jstring>(jniBridge->callObjectMethod(charSequence, charSequence_toString));
768 }
769
770 private:
771 jclass clipboardManagerClass = nullptr;
772 };
773
getClipboard()774 SkString* Engine::getClipboard() {
775 if (!checkJniBridge()) {
776 return nullptr;
777 }
778 SkString* result = nullptr;
779 auto bridge = JNIBridgeClipboardPaste(jniBridge);
780 auto clipboard = bridge.getSystemService(app->activity->clazz, "clipboard");
781 if (bridge.hasPrimaryClip(clipboard) && (
782 bridge.hasMimeType(bridge.getPrimaryClipDescription(clipboard), "text/plain") ||
783 bridge.hasMimeType(bridge.getPrimaryClipDescription(clipboard), "text/html"))) {
784 auto clipData = bridge.getPrimaryClip(clipboard);
785 auto item = bridge.getClipDataItemAt(clipData, 0);
786 auto jStr = bridge.getClipDataItemText(item);
787 auto cStr = jniBridge->getStringUTFChars(jStr, nullptr);
788 if (cStr) {
789 result = new SkString(cStr);
790 jniBridge->releaseStringUTFChars(jStr, cStr);
791 }
792 }
793 return result;
794 }
795
796 class JNIBridgeClipboardCopy : public JNIBridgeWrapper {
797 public:
798 using JNIBridgeWrapper::JNIBridgeWrapper;
799
newPlainText(const char * text)800 jobject newPlainText(const char* text) {
801 auto clipDataClass = jniBridge->findClass("android/content/ClipData");
802 auto clipData_newPlainText = jniBridge->getStaticMethodID(
803 clipDataClass, "newPlainText",
804 "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;");
805 return jniBridge->callStaticObjectMethod(
806 clipDataClass, clipData_newPlainText, jniBridge->makeString(""), jniBridge->makeString(text));
807 }
808
setPrimaryClip(jobject clipboard,jobject clipData)809 void setPrimaryClip(jobject clipboard, jobject clipData) {
810 auto clipboardManagerClass = jniBridge->findClass("android/content/ClipboardManager");
811 auto clipboardManager_setPrimaryClip = jniBridge->getMethodID(
812 clipboardManagerClass, "setPrimaryClip", "(Landroid/content/ClipData;)V");
813 jniBridge->callVoidMethod(clipboard, clipboardManager_setPrimaryClip, clipData);
814 }
815 };
816
setClipboard(const SkString & str)817 void Engine::setClipboard(const SkString& str) {
818 if (!checkJniBridge()) {
819 return;
820 }
821 auto bridge = JNIBridgeClipboardCopy(jniBridge);
822 auto clipData = bridge.newPlainText(str.c_str());
823 auto clipboard = bridge.getSystemService(app->activity->clazz, "clipboard");
824 bridge.setPrimaryClip(clipboard, clipData);
825 }
826
requestHaptic(int32_t p1,int32_t p2)827 void Engine::requestHaptic(int32_t p1, int32_t p2) {
828 if (!checkJniBridge()) {
829 return;
830 }
831
832 if (!vibratorInstance) {
833 auto* contextClass = jniBridge->findClass("android/content/Context");
834 auto* getSystemServiceMethod = jniBridge->getMethodID(
835 contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
836 auto androidContext = this->app->activity->clazz;
837 jstring vibratorString = jniBridge->makeString("vibrator");
838 jvalue arg;
839 arg.l = vibratorString;
840 vibratorInstance = jniBridge->callObjectMethod(androidContext, getSystemServiceMethod, arg);
841 if (!jniBridge->check()) {
842 return;
843 }
844 auto* vibratorClass = jniBridge->findClass("android/os/Vibrator");
845 if (!vibratorClass) {
846 LOGE("No android/os/Vibrator");
847 return;
848 }
849 vibrateWithDurationMethod = jniBridge->getMethodID(vibratorClass, "vibrate", "(J)V");
850 vibrateWithEffectMethod = jniBridge->getMethodID(vibratorClass, "vibrate", "(Landroid/os/VibrationEffect;)V");
851 if (!vibrateWithEffectMethod) {
852 LOGE("No vibrate(VibrationEffect) method");
853 return;
854 }
855 vibrationEffectClass = jniBridge->findClass("android/os/VibrationEffect");
856 if (!vibrationEffectClass) {
857 LOGE("No android/os/VibrationEffect class");
858 return;
859 }
860 createPredefinedEffectMethod = jniBridge->getStaticMethodID(vibrationEffectClass, "createPredefined", "(I)Landroid/os/VibrationEffect;");
861 if (!createPredefinedEffectMethod) {
862 LOGE("No createPredefined(int) method");
863 jniBridge->check();
864 return;
865 }
866 }
867 if (vibratorInstance != nullptr && vibrateWithDurationMethod != nullptr && p1 != 0) {
868 jvalue arg;
869 arg.j = p1;
870 jniBridge->callVoidMethod(vibratorInstance, vibrateWithDurationMethod, arg);
871 } else {
872 if (vibratorInstance != nullptr && vibrateWithEffectMethod != nullptr && createPredefinedEffectMethod != nullptr) {
873 jvalue arg;
874 arg.i = p2;
875 arg.l = jniBridge->callStaticObjectMethod(vibrationEffectClass, createPredefinedEffectMethod, arg);
876 if (!jniBridge->check()) return;
877 jniBridge->callVoidMethod(vibratorInstance, vibrateWithEffectMethod, arg);
878 }
879 }
880 }
881
getOrientation()882 int32_t Engine::getOrientation() {
883 if (!checkJniBridge()) {
884 return -1;
885 }
886 jclass displayClass = jniBridge->findClass("android/view/Display");
887 auto activity = this->app->activity->clazz;
888 jclass activityClass = jniBridge->getObjectClass(activity);
889 jmethodID getWindowManager = jniBridge->getMethodID(activityClass, "getWindowManager", "()Landroid/view/WindowManager;");
890 if (!jniBridge->check()) return -1;
891 jclass windowManagerClass = jniBridge->findClass("android/view/WindowManager");
892 if (!jniBridge->check()) return -1;
893 jmethodID getDefaultDisplay = jniBridge->getMethodID(windowManagerClass,"getDefaultDisplay","()Landroid/view/Display;");
894 if (!jniBridge->check()) return -1;
895 jmethodID getRotation = jniBridge->getMethodID(displayClass, "getRotation", "()I");
896 if (!jniBridge->check()) return -1;
897 jobject windowManager = jniBridge->callObjectMethod(activity, getWindowManager);
898 if (!jniBridge->check() || !windowManager) return -1;
899 jobject display = jniBridge->callObjectMethod(windowManager, getDefaultDisplay);
900 if (!jniBridge->check() || !display) return -1;
901 return jniBridge->callIntMethod(display, getRotation);
902 }
903
make_current(void * enginePtr,void * context)904 static bool make_current(void* enginePtr, void* context) {
905 Engine* engine = (Engine *)enginePtr;
906 return engine->makeCurrent(context != nullptr);
907 }
908
swap_buffers(void * enginePtr,void * context)909 static void swap_buffers(void* enginePtr, void* context) {
910 Engine* engine = (Engine *)enginePtr;
911 engine->swapBuffers();
912 }
913
check_events(void * enginePtr,void * context)914 static bool check_events(void* enginePtr, void* context) {
915 Engine* engine = (Engine *)enginePtr;
916 return engine->checkEvents();
917 }
918
get_content_scale(void * enginePtr,void * context)919 static float get_content_scale(void* enginePtr, void* context) {
920 Engine* engine = (Engine *)enginePtr;
921 return engine->getContentScale();
922 }
923
get_frame(void * enginePtr,void * peerPtr,int width,int height,int flags,int placement,int x,int y)924 static KNativePointer get_frame(void* enginePtr, void* peerPtr, int width, int height, int flags, int placement, int x, int y) {
925 Engine* engine = reinterpret_cast<Engine*>(enginePtr);
926 return engine->getFrame(peerPtr, width, height);
927 }
928
get_frame_width(void * enginePtr,void * window)929 static int32_t get_frame_width(void* enginePtr, void* window) {
930 Engine* engine = (Engine *)enginePtr;
931 return engine->getFrameWidth();
932 }
933
get_frame_height(void * enginePtr,void * window)934 static int32_t get_frame_height(void* enginePtr, void* window) {
935 Engine* engine = (Engine*)enginePtr;
936 return engine->getFrameHeight();
937 }
938
get_physical_width(void * enginePtr,void * window)939 static int32_t get_physical_width(void* enginePtr, void* window) {
940 Engine* engine = (Engine *)enginePtr;
941 return engine->getPhysicalWidth();
942 }
943
get_physical_height(void * enginePtr,void * window)944 static int32_t get_physical_height(void* enginePtr, void* window) {
945 Engine* engine = (Engine*)enginePtr;
946 return engine->getPhysicalHeight();
947 }
948
request_haptic(void * enginePtr,int p1,int p2)949 static void request_haptic(void* enginePtr, int p1, int p2) {
950 Engine* engine = (Engine*)enginePtr;
951 return engine->requestHaptic(p1, p2);
952 }
953
get_orientation(void * enginePtr)954 static int32_t get_orientation(void* enginePtr) {
955 Engine* engine = (Engine*)enginePtr;
956 return engine->getOrientation();
957 }
958
soft_keyboard(void * enginePtr,bool show)959 static void soft_keyboard(void* enginePtr, bool show) {
960 Engine* engine = (Engine*)enginePtr;
961 return engine->softKeyboard(show);
962 }
963
commit_input(void * enginePtr)964 static void commit_input(void* enginePtr) {
965 Engine* engine = (Engine*)enginePtr;
966 return engine->commitInput();
967 }
968
stringPermissions(KStringArray strArray)969 static std::vector<std::string> stringPermissions(KStringArray strArray) {
970 if (strArray == nullptr) {
971 return std::vector<std::string>(0);
972 }
973
974 KUInt arraySize = skoala::unpackUInt(strArray);
975 std::vector<std::string> res(arraySize);
976 size_t offset = sizeof(KUInt);
977 for (KUInt i = 0; i < arraySize; ++i) {
978 auto s = strArray + offset;
979 auto* str = reinterpret_cast<const char *>(&s[sizeof(KUInt)]);
980 res[i] = std::string(str, skoala::unpackUInt(s));
981 offset += res[i].size() + sizeof(KUInt);
982 }
983
984 return res;
985 }
986
askPermissions(void * enginePtr,KStringArray perms)987 static void askPermissions(void* enginePtr, KStringArray perms) {
988 Engine* engine = (Engine*)enginePtr;
989 auto vector_perms = stringPermissions(perms);
990 std::vector<std::string> missed_perms;
991 std::copy_if(
992 vector_perms.begin(),
993 vector_perms.end(),
994 std::back_inserter(missed_perms),
995 [engine](std::string s) {
996 return engine->androidExistPermission(s.c_str());
997 });
998
999 if (!missed_perms.empty()) {
1000 engine->askPermissions(missed_perms);
1001 }
1002 }
1003
get_clipboard(void * enginePtr,void * context)1004 static SkString* get_clipboard(void* enginePtr, void* context) {
1005 Engine* engine = reinterpret_cast<Engine*>(enginePtr);
1006 return engine->getClipboard();
1007 }
1008
set_clipboard(void * enginePtr,void * context,const SkString & str)1009 static void set_clipboard(void* enginePtr, void* context, const SkString& str) {
1010 Engine* engine = reinterpret_cast<Engine*>(enginePtr);
1011 return engine->setClipboard(str);
1012 }
1013
get_global_pointer(void * enginePtr,KInt * pointer)1014 static void get_global_pointer(void* enginePtr, KInt* pointer) {
1015 Engine* engine = reinterpret_cast<Engine*>(enginePtr);
1016 engine->globalPointer(pointer[0], pointer[1]);
1017 }
1018
peer_factory(void * ctx,v8::Local<v8::Object> redrawer)1019 RedrawerPeer* Engine::peer_factory(void* ctx, v8::Local<v8::Object> redrawer) {
1020 Engine* engine = (Engine*)ctx;
1021
1022 RedrawerPeer* peer = new RedrawerPeer(engine->nodeScopedState.get(), redrawer);
1023 engine->peer = peer;
1024 peer->engine = engine;
1025
1026 peer->getFrame = ::get_frame;
1027 peer->makeCurrent = ::make_current;
1028 peer->swapBuffers = ::swap_buffers;
1029 peer->checkEvents = ::check_events;
1030 peer->getContentScale = ::get_content_scale;
1031 peer->getFrameWidth = ::get_frame_width;
1032 peer->getFrameHeight = ::get_frame_height;
1033 peer->getPhysicalWidth = ::get_physical_width;
1034 peer->getPhysicalHeight = ::get_physical_height;
1035 peer->requestHaptic = ::request_haptic;
1036 peer->getOrientation = ::get_orientation;
1037 peer->softKeyboard = ::soft_keyboard;
1038 peer->commitInput = ::commit_input;
1039 peer->askPermissions = ::askPermissions;
1040 peer->readFromClipboard = ::get_clipboard;
1041 peer->writeToClipboard = ::set_clipboard;
1042 peer->getGlobalPointer = get_global_pointer;
1043
1044 return peer;
1045 }
1046
1047 using namespace v8;
1048
prepareNode(std::vector<std::string> & errors,const std::string & startScript)1049 int Engine::prepareNode(std::vector<std::string> &errors, const std::string& startScript) {
1050 node::CommonEnvironmentSetup* setup = nodeRuntime->setup;
1051 Isolate* isolate = setup->isolate();
1052 node::Environment* env = setup->env();
1053 v8::HandleScope handle_scope(isolate);
1054 std::string mainScript =
1055 "const publicRequire = require('module').createRequire(process.cwd());"
1056 "globalThis.require = publicRequire;"
1057 "return require('vm').runInThisContext('try { require(\"" + startScript + "\"); global.platformAPI(); } catch (e) { console.log(e) }');"
1058 ;
1059 MaybeLocal<v8::Value> maybeResult = node::LoadEnvironment(env, mainScript.c_str());
1060 if (maybeResult.IsEmpty()) {
1061 LOGE("Cannot load environment");
1062 return 1; // There has been a JS exception.
1063 }
1064 Local<v8::Value> result;
1065 if (!maybeResult.ToLocal(&result)) {
1066 LOGE("Cannot convert to local");
1067 return -1;
1068 }
1069 if (result->IsObject()) {
1070 nodeScopedState->startCallbacks();
1071 nodeScopedState->initPlatform(result.As<v8::Object>());
1072 nodeScopedState->providePlatformData(peer_factory, this);
1073 }
1074 return 0;
1075 }