• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }