1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-20 05:23:04 +01:00
Files
OpenRCT2/src/openrct2/platform/Platform.Android.cpp
Duncan d5de6c2b49 Start Simplifying CMake code (#24177)
* Use generator expressions

* Update cmake minimum

* Move options and remove project name

* Use further generator expressions

* Try upgrading to CMake 3.25 on Ubuntu 22.04 CI

Install software-properties-common

* Standardise with DISABLE_TTF for disable defines

* Set X64 var differently due to unknown reasons

---------

Co-authored-by: Michael Steenbeek <1478678+Gymnasiast@users.noreply.github.com>
2025-04-11 16:09:52 +01:00

262 lines
8.6 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2025 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#ifdef __ANDROID__
#include "Platform.h"
#include "../Diagnostic.h"
#include "../core/File.h"
#include "../core/Guard.hpp"
#include "../localisation/Language.h"
#include <SDL.h>
#include <jni.h>
#include <memory>
AndroidClassLoader::~AndroidClassLoader()
{
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
env->DeleteGlobalRef(_classLoader);
}
jobject AndroidClassLoader::_classLoader;
jmethodID AndroidClassLoader::_findClassMethod;
// Initialized in JNI_OnLoad. Cannot be initialized here as JVM is not
// available until after JNI_OnLoad is called.
static std::shared_ptr<AndroidClassLoader> acl;
namespace OpenRCT2::Platform
{
std::string GetFolderPath(SpecialFolder folder)
{
// Android builds currently only read from /sdcard/openrct2*
switch (folder)
{
case SpecialFolder::userCache:
case SpecialFolder::userConfig:
case SpecialFolder::userData:
case SpecialFolder::userHome:
return "/sdcard";
default:
return std::string();
}
}
std::string GetDocsPath()
{
return std::string();
}
std::string GetInstallPath()
{
return "/sdcard/openrct2";
}
std::string GetCurrentExecutablePath()
{
Guard::Assert(false, "GetCurrentExecutablePath() not implemented for Android.");
return std::string();
}
u8string StrDecompToPrecomp(u8string_view input)
{
return u8string(input);
}
bool HandleSpecialCommandLineArgument(const char* argument)
{
return false;
}
uint16_t GetLocaleLanguage()
{
JNIEnv* env = static_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
jobject activity = static_cast<jobject>(SDL_AndroidGetActivity());
jclass activityClass = env->GetObjectClass(activity);
jmethodID getDefaultLocale = env->GetMethodID(
activityClass, "getDefaultLocale", "([Ljava/lang/String;)Ljava/lang/String;");
jobjectArray jLanguageTags = env->NewObjectArray(
LANGUAGE_COUNT, env->FindClass("java/lang/String"), env->NewStringUTF(""));
for (int32_t i = 1; i < LANGUAGE_COUNT; ++i)
{
jstring jTag = env->NewStringUTF(LanguagesDescriptors[i].locale);
env->SetObjectArrayElement(jLanguageTags, i, jTag);
}
jstring jniString = static_cast<jstring>(env->CallObjectMethod(activity, getDefaultLocale, jLanguageTags));
const char* jniChars = env->GetStringUTFChars(jniString, nullptr);
std::string defaultLocale = jniChars;
env->ReleaseStringUTFChars(jniString, jniChars);
for (int32_t i = 0; i < LANGUAGE_COUNT; ++i)
{
jobject strToFree = env->GetObjectArrayElement(jLanguageTags, i);
env->DeleteLocalRef(strToFree);
}
env->DeleteLocalRef(jLanguageTags);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(activityClass);
return LanguageGetIDFromLocale(defaultLocale.c_str());
}
CurrencyType GetLocaleCurrency()
{
JNIEnv* env = static_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
jobject activity = static_cast<jobject>(SDL_AndroidGetActivity());
jclass activityClass = env->GetObjectClass(activity);
jmethodID getDefaultLocale = env->GetMethodID(activityClass, "getLocaleCurrency", "()Ljava/lang/String;");
jstring jniString = static_cast<jstring>(env->CallObjectMethod(activity, getDefaultLocale));
const char* jniChars = env->GetStringUTFChars(jniString, nullptr);
std::string localeCurrencyCode = jniChars;
env->ReleaseStringUTFChars(jniString, jniChars);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(activityClass);
return Platform::GetCurrencyValue(localeCurrencyCode.c_str());
}
MeasurementFormat GetLocaleMeasurementFormat()
{
JNIEnv* env = static_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
jobject activity = static_cast<jobject>(SDL_AndroidGetActivity());
jclass activityClass = env->GetObjectClass(activity);
jmethodID getIsImperialLocaleMeasurementFormat = env->GetMethodID(
activityClass, "isImperialLocaleMeasurementFormat", "()Z");
jboolean isImperial = env->CallBooleanMethod(activity, getIsImperialLocaleMeasurementFormat);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(activityClass);
return isImperial == JNI_TRUE ? MeasurementFormat::Imperial : MeasurementFormat::Metric;
}
std::string GetSteamPath()
{
return {};
}
u8string GetRCT1SteamDir()
{
return {};
}
u8string GetRCT2SteamDir()
{
return {};
}
#ifndef DISABLE_TTF
std::string GetFontPath(const TTFFontDescriptor& font)
{
auto expectedPath = std::string("/system/fonts/") + std::string(font.filename);
if (File::Exists(expectedPath))
{
return expectedPath;
}
return "";
}
#endif
float GetDefaultScale()
{
JNIEnv* env = static_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
jobject activity = static_cast<jobject>(SDL_AndroidGetActivity());
jclass activityClass = env->GetObjectClass(activity);
jmethodID getDefaultScale = env->GetMethodID(activityClass, "getDefaultScale", "()F");
jfloat displayScale = env->CallFloatMethod(activity, getDefaultScale);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(activityClass);
return displayScale;
}
jclass AndroidFindClass(JNIEnv* env, std::string_view name)
{
return static_cast<jclass>(env->CallObjectMethod(
AndroidClassLoader::_classLoader, AndroidClassLoader::_findClassMethod,
env->NewStringUTF(std::string(name).c_str())));
}
std::vector<std::string_view> GetSearchablePathsRCT1()
{
return { "/sdcard/rct1" };
}
std::vector<std::string_view> GetSearchablePathsRCT2()
{
return { "/sdcard/rct2" };
}
} // namespace OpenRCT2::Platform
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved)
{
// Due to an issue where JNI_OnLoad could be called multiple times, we need
// to make sure it is only initialized once.
// https://issuetracker.google.com/issues/220523932
// Otherwise JVM complains about jobject-s having incorrect serial numbers.
if (!acl)
{
acl = std::make_shared<AndroidClassLoader>();
}
return JNI_VERSION_1_6;
}
AndroidClassLoader::AndroidClassLoader()
{
LOG_INFO("Obtaining JNI class loader");
// This is a workaround to be able to call JNI's ClassLoader from non-main
// thread, based on https://stackoverflow.com/a/16302771
// Apparently it's OK to use it from across different thread, but JNI
// only looks for ClassLoader in the _current_ thread and fails to find
// it when searched for from a native library's non-main thread.
// The solution below works by obtaining a ClassLoader reference in main
// thread and caching it for future use from any thread, instead of using
// it via env->FindClass(). ClassLoader itself is abstract, so we cannot
// create it directly; instead we take an arbitrary class and call
// getClassLoader() on it to create a reference that way.
// If we're here, SDL's JNI_OnLoad has already been called and set env
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
// Take an arbitrary class. While the class does not really matter, it
// makes sense to use one that's most likely already loaded and is unlikely
// to be removed from code.
auto randomClass = env->FindClass("io/openrct2/MainActivity");
jclass classClass = env->GetObjectClass(randomClass);
// Get its class loader
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
// Store the class loader and its findClass method for future use
_classLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
_findClassMethod = env->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
}
#endif