|
|
|
|
#include "init.hpp"
|
|
|
|
|
#include "mpq/mpq_reader.hpp"
|
|
|
|
|
|
|
|
|
|
#include <jni.h>
|
|
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
// Global Java VM pointer - set during JNI initialization
|
|
|
|
|
static JavaVM *g_jvm = nullptr;
|
|
|
|
|
static jobject g_activity = nullptr;
|
|
|
|
|
|
|
|
|
|
// JNI method cache for accessibility functions
|
|
|
|
|
struct AndroidJNIMethods {
|
|
|
|
|
jmethodID isScreenReaderEnabled;
|
|
|
|
|
jmethodID accessibilitySpeak;
|
|
|
|
|
bool initialized;
|
|
|
|
|
} g_jniMethods = { nullptr, nullptr, false };
|
|
|
|
|
|
|
|
|
|
// Thread-local storage for JNIEnv
|
|
|
|
|
static pthread_key_t g_jniEnvKey;
|
|
|
|
|
|
|
|
|
|
// Initialize JNI environment key
|
|
|
|
|
static void JNIKeyDestructor(void *env)
|
|
|
|
|
{
|
|
|
|
|
// Don't detach - let SDL handle it
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void InitializeJNIKey()
|
|
|
|
|
{
|
|
|
|
|
static bool initialized = false;
|
|
|
|
|
if (initialized)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pthread_key_create(&g_jniEnvKey, JNIKeyDestructor);
|
|
|
|
|
initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get JNI environment for current thread
|
|
|
|
|
static JNIEnv* GetJNI()
|
|
|
|
|
{
|
|
|
|
|
InitializeJNIKey();
|
|
|
|
|
|
|
|
|
|
JNIEnv *env = (JNIEnv *)pthread_getspecific(g_jniEnvKey);
|
|
|
|
|
if (env)
|
|
|
|
|
return env;
|
|
|
|
|
|
|
|
|
|
if (g_jvm == nullptr)
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
// Get or attach the current thread
|
|
|
|
|
int status = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
|
|
|
|
if (status == JNI_EDETACHED) {
|
|
|
|
|
// Thread not attached, attach it
|
|
|
|
|
status = g_jvm->AttachCurrentThread(&env, nullptr);
|
|
|
|
|
if (status < 0)
|
|
|
|
|
return nullptr;
|
|
|
|
|
pthread_setspecific(g_jniEnvKey, env);
|
|
|
|
|
} else if (status != JNI_OK) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return env;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool AreExtraFontsOutOfDateForMpqPath(const char *mpqPath)
|
|
|
|
|
{
|
|
|
|
|
int32_t error = 0;
|
|
|
|
|
std::optional<MpqArchive> archive = MpqArchive::Open(mpqPath, error);
|
|
|
|
|
return error == 0 && archive && AreExtraFontsOutOfDate(*archive);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize JNI method IDs for accessibility
|
|
|
|
|
// This should be called once during initialization
|
|
|
|
|
static void InitializeAccessibilityJNI(JNIEnv *env)
|
|
|
|
|
{
|
|
|
|
|
if (g_jniMethods.initialized)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Get the DevilutionXSDLActivity class
|
|
|
|
|
jclass activityClass = env->FindClass("org/diasurgical/devilutionx/DevilutionXSDLActivity");
|
|
|
|
|
if (activityClass == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cache method IDs for accessibility functions
|
|
|
|
|
g_jniMethods.isScreenReaderEnabled = env->GetMethodID(activityClass, "isScreenReaderEnabled", "()Z");
|
|
|
|
|
g_jniMethods.accessibilitySpeak = env->GetMethodID(activityClass, "accessibilitySpeak", "(Ljava/lang/String;)V");
|
|
|
|
|
|
|
|
|
|
if (g_jniMethods.isScreenReaderEnabled && g_jniMethods.accessibilitySpeak) {
|
|
|
|
|
g_jniMethods.initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
env->DeleteLocalRef(activityClass);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public accessibility functions for Android
|
|
|
|
|
namespace accessibility {
|
|
|
|
|
|
|
|
|
|
bool InitializeScreenReaderAndroid()
|
|
|
|
|
{
|
|
|
|
|
// JNI is initialized when nativeInitAccessibility is called from Java
|
|
|
|
|
// This function is kept for compatibility but the actual initialization
|
|
|
|
|
// happens in Java_org_diasurgical_devilutionx_DevilutionXSDLActivity_nativeInitAccessibility
|
|
|
|
|
return g_jniMethods.initialized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ShutDownScreenReaderAndroid()
|
|
|
|
|
{
|
|
|
|
|
// Clean up the activity reference
|
|
|
|
|
if (g_activity != nullptr) {
|
|
|
|
|
JNIEnv *env = GetJNI();
|
|
|
|
|
if (env != nullptr) {
|
|
|
|
|
env->DeleteGlobalRef(g_activity);
|
|
|
|
|
}
|
|
|
|
|
g_activity = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_jniMethods.initialized = false;
|
|
|
|
|
g_jniMethods.isScreenReaderEnabled = nullptr;
|
|
|
|
|
g_jniMethods.accessibilitySpeak = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpeakTextAndroid(const char *text)
|
|
|
|
|
{
|
|
|
|
|
if (!g_jniMethods.initialized)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
JNIEnv *env = GetJNI();
|
|
|
|
|
if (env == nullptr || g_activity == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Create a Java string from the text
|
|
|
|
|
jstring jText = env->NewStringUTF(text);
|
|
|
|
|
if (jText == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Call the accessibilitySpeak method
|
|
|
|
|
env->CallVoidMethod(g_activity, g_jniMethods.accessibilitySpeak, jText);
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
env->DeleteLocalRef(jText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsScreenReaderEnabledAndroid()
|
|
|
|
|
{
|
|
|
|
|
if (!g_jniMethods.initialized)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
JNIEnv *env = GetJNI();
|
|
|
|
|
if (env == nullptr || g_activity == nullptr)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Call the isScreenReaderEnabled method
|
|
|
|
|
jboolean result = env->CallBooleanMethod(g_activity, g_jniMethods.isScreenReaderEnabled);
|
|
|
|
|
|
|
|
|
|
return result == JNI_TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace accessibility
|
|
|
|
|
} // namespace devilution
|
|
|
|
|
|
|
|
|
|
// JNI initialization function called from Java during Activity initialization
|
|
|
|
|
extern "C" {
|
|
|
|
|
JNIEXPORT void JNICALL Java_org_diasurgical_devilutionx_DevilutionXSDLActivity_nativeInitAccessibility(
|
|
|
|
|
JNIEnv *env, jobject thiz)
|
|
|
|
|
{
|
|
|
|
|
// Store the Java VM pointer
|
|
|
|
|
if (devilution::g_jvm == nullptr) {
|
|
|
|
|
env->GetJavaVM(&devilution::g_jvm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store a global reference to the activity
|
|
|
|
|
if (devilution::g_activity != nullptr) {
|
|
|
|
|
env->DeleteGlobalRef(devilution::g_activity);
|
|
|
|
|
}
|
|
|
|
|
devilution::g_activity = env->NewGlobalRef(thiz);
|
|
|
|
|
|
|
|
|
|
// Initialize the JNI method cache
|
|
|
|
|
devilution::InitializeAccessibilityJNI(env);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL Java_org_diasurgical_devilutionx_DevilutionXSDLActivity_areFontsOutOfDate(JNIEnv *env, jclass cls, jstring fonts_mpq)
|
|
|
|
|
{
|
|
|
|
|
const char *mpqPath = env->GetStringUTFChars(fonts_mpq, nullptr);
|
|
|
|
|
bool outOfDate = devilution::AreExtraFontsOutOfDateForMpqPath(mpqPath);
|
|
|
|
|
env->ReleaseStringUTFChars(fonts_mpq, mpqPath);
|
|
|
|
|
return outOfDate;
|
|
|
|
|
}
|
|
|
|
|
}
|