Modules/Graphics/private/android/AndroidPlatform.cpp
Шурупов Илья Викторович 67a86dea2d Use the whole window in the studio layout
The window now extends into the display cutout and the top bars sit at
the top edge with the gap fixed at the screen center. Top buttons are at
most 0.8 of the bottom bar buttons. The cutout query is removed again.
2026-10-18 22:24:25 +03:00

216 lines
8.5 KiB
C++

#include "AndroidPlatform.hpp"
#include <android/asset_manager.h>
#include <android/input.h>
#include <android/log.h>
#include <android/native_activity.h>
#include <android_native_app_glue.h>
#include <jni.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <vector>
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, "<init>", "(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<char> 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());
}
}