#include "AndroidPlatform.hpp" #include #include #include #include #include #include #include #include #include #include namespace tp::android { namespace { android_app* gApplication = nullptr; struct AttachedEnv { explicit AttachedEnv(ANativeActivity* activity) : vm(activity->vm) { if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) == JNI_EDETACHED) { vm->AttachCurrentThread(&env, nullptr); attached = true; } } ~AttachedEnv() { if (attached) vm->DetachCurrentThread(); } JavaVM* vm; JNIEnv* env = nullptr; bool attached = false; }; void appendUtf8(std::string& out, uint32_t codepoint) { if (codepoint < 0x80) { out += (char) codepoint; } else if (codepoint < 0x800) { out += (char) (0xC0 | (codepoint >> 6)); out += (char) (0x80 | (codepoint & 0x3F)); } else if (codepoint < 0x10000) { out += (char) (0xE0 | (codepoint >> 12)); out += (char) (0x80 | ((codepoint >> 6) & 0x3F)); out += (char) (0x80 | (codepoint & 0x3F)); } else { out += (char) (0xF0 | (codepoint >> 18)); out += (char) (0x80 | ((codepoint >> 12) & 0x3F)); out += (char) (0x80 | ((codepoint >> 6) & 0x3F)); out += (char) (0x80 | (codepoint & 0x3F)); } } } android_app* application() { return gApplication; } void setApplication(android_app* app) { gApplication = app; } std::string unicodeText(android_app& app, const AInputEvent* event) { AttachedEnv attached(app.activity); JNIEnv* env = attached.env; if (!env) return {}; jclass keyEventClass = env->FindClass("android/view/KeyEvent"); jmethodID constructor = env->GetMethodID(keyEventClass, "", "(JJIIIIIIII)V"); jmethodID getUnicodeChar = env->GetMethodID(keyEventClass, "getUnicodeChar", "(I)I"); jobject keyEvent = env->NewObject( keyEventClass, constructor, (jlong) AKeyEvent_getDownTime(event), (jlong) AKeyEvent_getEventTime(event), (jint) AKeyEvent_getAction(event), (jint) AKeyEvent_getKeyCode(event), (jint) AKeyEvent_getRepeatCount(event), (jint) AKeyEvent_getMetaState(event), (jint) AInputEvent_getDeviceId(event), (jint) AKeyEvent_getScanCode(event), (jint) AKeyEvent_getFlags(event), (jint) AInputEvent_getSource(event) ); const jint codepoint = env->CallIntMethod(keyEvent, getUnicodeChar, (jint) AKeyEvent_getMetaState(event)); env->DeleteLocalRef(keyEvent); env->DeleteLocalRef(keyEventClass); std::string text; if (codepoint > 0 && codepoint >= 0x20 && codepoint != 0x7F) appendUtf8(text, (uint32_t) codepoint); return text; } void setSoftKeyboardVisible(android_app& app, bool visible) { AttachedEnv attached(app.activity); JNIEnv* env = attached.env; if (!env) return; jobject activity = app.activity->clazz; jclass activityClass = env->GetObjectClass(activity); jclass contextClass = env->FindClass("android/content/Context"); jfieldID serviceField = env->GetStaticFieldID(contextClass, "INPUT_METHOD_SERVICE", "Ljava/lang/String;"); jobject serviceName = env->GetStaticObjectField(contextClass, serviceField); jmethodID getSystemService = env->GetMethodID(activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); jobject inputManager = env->CallObjectMethod(activity, getSystemService, serviceName); jmethodID getWindow = env->GetMethodID(activityClass, "getWindow", "()Landroid/view/Window;"); jobject window = env->CallObjectMethod(activity, getWindow); jclass windowClass = env->FindClass("android/view/Window"); jmethodID getDecorView = env->GetMethodID(windowClass, "getDecorView", "()Landroid/view/View;"); jobject decorView = env->CallObjectMethod(window, getDecorView); jclass managerClass = env->FindClass("android/view/inputmethod/InputMethodManager"); if (visible) { jmethodID show = env->GetMethodID(managerClass, "showSoftInput", "(Landroid/view/View;I)Z"); env->CallBooleanMethod(inputManager, show, decorView, 0); } else { jclass viewClass = env->FindClass("android/view/View"); jmethodID getToken = env->GetMethodID(viewClass, "getWindowToken", "()Landroid/os/IBinder;"); jobject token = env->CallObjectMethod(decorView, getToken); jmethodID hide = env->GetMethodID(managerClass, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z"); env->CallBooleanMethod(inputManager, hide, token, 0); env->DeleteLocalRef(token); env->DeleteLocalRef(viewClass); } if (env->ExceptionCheck()) env->ExceptionClear(); env->DeleteLocalRef(managerClass); env->DeleteLocalRef(decorView); env->DeleteLocalRef(windowClass); env->DeleteLocalRef(window); env->DeleteLocalRef(inputManager); env->DeleteLocalRef(serviceName); env->DeleteLocalRef(contextClass); env->DeleteLocalRef(activityClass); } EdgeInsets systemInsets(android_app& app) { AttachedEnv attached(app.activity); JNIEnv* env = attached.env; if (!env) return {}; EdgeInsets result; jobject activity = app.activity->clazz; jclass activityClass = env->GetObjectClass(activity); jmethodID getWindow = env->GetMethodID(activityClass, "getWindow", "()Landroid/view/Window;"); jobject window = env->CallObjectMethod(activity, getWindow); jclass windowClass = env->FindClass("android/view/Window"); jmethodID getDecorView = env->GetMethodID(windowClass, "getDecorView", "()Landroid/view/View;"); jobject decorView = window ? env->CallObjectMethod(window, getDecorView) : nullptr; jclass viewClass = env->FindClass("android/view/View"); jmethodID getInsets = env->GetMethodID(viewClass, "getRootWindowInsets", "()Landroid/view/WindowInsets;"); jobject insets = decorView ? env->CallObjectMethod(decorView, getInsets) : nullptr; if (insets) { jclass typeClass = env->FindClass("android/view/WindowInsets$Type"); const auto typeMask = [&](const char* name) { return env->CallStaticIntMethod(typeClass, env->GetStaticMethodID(typeClass, name, "()I")); }; const jint bars = typeMask("systemBars") | typeMask("displayCutout"); const jint gestures = typeMask("mandatorySystemGestures"); jclass insetsClass = env->FindClass("android/view/WindowInsets"); jmethodID getInsetsOf = env->GetMethodID(insetsClass, "getInsets", "(I)Landroid/graphics/Insets;"); jobject barEdges = env->CallObjectMethod(insets, getInsetsOf, bars); jobject gestureEdges = env->CallObjectMethod(insets, getInsetsOf, gestures); jclass edgesClass = env->FindClass("android/graphics/Insets"); const auto edge = [&](jobject edges, const char* name) { return edges ? (halnf) env->GetIntField(edges, env->GetFieldID(edgesClass, name, "I")) : 0.0f; }; const auto both = [&](const char* name) { return std::max(edge(barEdges, name), edge(gestureEdges, name)); }; result = { both("left"), both("top"), both("right"), both("bottom") }; env->DeleteLocalRef(edgesClass); if (gestureEdges) env->DeleteLocalRef(gestureEdges); if (barEdges) env->DeleteLocalRef(barEdges); env->DeleteLocalRef(insetsClass); env->DeleteLocalRef(typeClass); env->DeleteLocalRef(insets); } if (env->ExceptionCheck()) env->ExceptionClear(); env->DeleteLocalRef(viewClass); if (decorView) env->DeleteLocalRef(decorView); env->DeleteLocalRef(windowClass); if (window) env->DeleteLocalRef(window); env->DeleteLocalRef(activityClass); return result; } namespace { void extractDirectory(AAssetManager* manager, const std::string& assetDirectory, const std::filesystem::path& target) { AAssetDir* directory = AAssetManager_openDir(manager, assetDirectory.c_str()); if (!directory) return; std::filesystem::create_directories(target); while (const char* name = AAssetDir_getNextFileName(directory)) { const std::string assetPath = assetDirectory.empty() ? name : assetDirectory + "/" + name; AAsset* asset = AAssetManager_open(manager, assetPath.c_str(), AASSET_MODE_STREAMING); if (!asset) continue; std::ofstream out(target / name, std::ios::binary | std::ios::trunc); std::vector buffer(64 * 1024); int read = 0; while ((read = AAsset_read(asset, buffer.data(), buffer.size())) > 0) out.write(buffer.data(), read); AAsset_close(asset); } AAssetDir_close(directory); } } void extractAssets(android_app& app, const char* directory) { AAssetManager* manager = app.activity->assetManager; const std::filesystem::path target = std::filesystem::path(app.activity->internalDataPath) / directory; extractDirectory(manager, directory, target); __android_log_print(ANDROID_LOG_INFO, "modules", "assets extracted to %s", target.c_str()); } }