Implement Platform::DefaultDdir for android

The default directory is acquired via getExternalFilesDir. Typical value: /storage/emulated/0/Android/data/uk.co.powdertoy.tpt/files, which seems to be user-accessible both on old and new Android. Probably. Hopefully. We'll see.
This commit is contained in:
Tamás Bálint Misius
2024-08-30 21:23:17 +02:00
parent 5180df9180
commit f58d4fbd63
5 changed files with 85 additions and 63 deletions

View File

@@ -10,7 +10,7 @@ import java.util.Base64;
public class PowderActivity extends SDLActivity public class PowderActivity extends SDLActivity
{ {
public static String getCertificateBundle() public String getCertificateBundle()
{ {
String allPems = ""; String allPems = "";
try { try {
@@ -39,4 +39,9 @@ public class PowderActivity extends SDLActivity
} }
return allPems; return allPems;
} }
public String getDefaultDdir()
{
return getExternalFilesDir(null).getAbsolutePath();
}
} }

View File

@@ -1,84 +1,34 @@
#include "CurlError.h" #include "CurlError.h"
#include "common/String.h" #include "common/platform/Android.h"
#include "Config.h" #include "Config.h"
#include <iostream> #include <iostream>
#include <SDL.h>
#include <jni.h>
#include <android/log.h> #include <android/log.h>
static jclass FindClass(JNIEnv *env, const char *name)
{
jobject nativeActivity = (jobject)SDL_AndroidGetActivity(); if (!nativeActivity) return NULL;
jclass acl = env->GetObjectClass(nativeActivity); if (!acl ) return NULL;
jmethodID getClassLoader = env->GetMethodID(acl, "getClassLoader", "()Ljava/lang/ClassLoader;"); if (!getClassLoader) return NULL;
jobject cls = env->CallObjectMethod(nativeActivity, getClassLoader); if (!cls ) return NULL;
jclass classLoader = env->FindClass("java/lang/ClassLoader"); if (!classLoader ) return NULL;
jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); if (!findClass ) return NULL;
jstring strClassName = env->NewStringUTF(name); if (!strClassName ) return NULL;
jclass clazz = (jclass)(env->CallObjectMethod(cls, findClass, strClassName));
env->DeleteLocalRef(strClassName);
return clazz;
}
namespace http namespace http
{ {
void UseSystemCertProvider(CURL *easy) void UseSystemCertProvider(CURL *easy)
{ {
struct DoOnce struct DoOnce
{ {
ByteString allPems; ByteString certificateBundle;
void InitPem()
{
auto die = [](ByteString message) {
__android_log_print(ANDROID_LOG_ERROR, "AndroidCertProvider", "%s", ("failed to enumerate system certificates: " + message).c_str());
};
auto *env = (JNIEnv *)SDL_AndroidGetJNIEnv();
if (!env)
{
return die("SDL_AndroidGetJNIEnv failed");
}
jclass mPowderActivity = FindClass(env, ByteString::Build(APPID, ".PowderActivity").c_str());
if (!mPowderActivity)
{
return die("FindClass failed");
}
jmethodID midGetCertificateBundle = env->GetStaticMethodID(mPowderActivity, "getCertificateBundle", "()Ljava/lang/String;");
if (!midGetCertificateBundle)
{
return die("GetStaticMethodID failed");
}
jstring str = (jstring)env->CallStaticObjectMethod(mPowderActivity, midGetCertificateBundle);
if (!str)
{
return die("getCertificateBundle failed");
}
const char *utf = env->GetStringUTFChars(str, 0);
if (utf)
{
allPems = utf;
env->ReleaseStringUTFChars(str, utf);
}
else
{
__android_log_print(ANDROID_LOG_ERROR, "AndroidCertProvider", "out of memory???");
}
env->DeleteLocalRef(str);
__android_log_print(ANDROID_LOG_ERROR, "AndroidCertProvider", "certificate bundle loaded");
}
DoOnce() DoOnce()
{ {
InitPem(); auto certificateBundleOpt = Platform::CallActivityStringFunc("getCertificateBundle");
if (certificateBundleOpt)
{
certificateBundle = *certificateBundleOpt;
__android_log_print(ANDROID_LOG_ERROR, APPID, "certificate bundle loaded");
}
} }
}; };
static DoOnce doOnce; static DoOnce doOnce;
if (doOnce.allPems.size()) if (doOnce.certificateBundle.size())
{ {
curl_blob blob; curl_blob blob;
blob.data = doOnce.allPems.data(); blob.data = doOnce.certificateBundle.data();
blob.len = doOnce.allPems.size(); blob.len = doOnce.certificateBundle.size();
blob.flags = CURL_BLOB_COPY; blob.flags = CURL_BLOB_COPY;
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_CAINFO_BLOB, &blob)); HandleCURLcode(curl_easy_setopt(easy, CURLOPT_CAINFO_BLOB, &blob));
} }

View File

@@ -1,5 +1,11 @@
#include "Platform.h" #include "Platform.h"
#include "Android.h"
#include "common/Defer.h"
#include "Config.h"
#include <ctime> #include <ctime>
#include <SDL.h>
#include <jni.h>
#include <android/log.h>
namespace Platform namespace Platform
{ {
@@ -28,4 +34,58 @@ bool CanUpdate()
void SetupCrt() void SetupCrt()
{ {
} }
std::optional<ByteString> CallActivityStringFunc(const char *funcName)
{
ByteString result;
struct CheckFailed : public std::runtime_error
{
using runtime_error::runtime_error;
};
try
{
auto CHECK = [](auto thing, const char *what) {
if (!thing)
{
throw CheckFailed(what);
}
return thing;
};
#define CHECK(a) CHECK(a, #a)
auto *env = CHECK((JNIEnv *)SDL_AndroidGetJNIEnv());
auto activityInst = CHECK((jobject)SDL_AndroidGetActivity());
auto activityCls = CHECK(env->GetObjectClass(activityInst));
auto getClassLoaderMth = CHECK(env->GetMethodID(activityCls, "getClassLoader", "()Ljava/lang/ClassLoader;"));
auto classLoaderInst = CHECK(env->CallObjectMethod(activityInst, getClassLoaderMth));
auto classLoaderCls = CHECK(env->FindClass("java/lang/ClassLoader"));
auto findClassMth = CHECK(env->GetMethodID(classLoaderCls, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"));
auto strClassName = CHECK(env->NewStringUTF(ByteString::Build(APPID, ".PowderActivity").c_str()));
Defer deleteStrClassName([env, strClassName]() { env->DeleteLocalRef(strClassName); });
auto mPowderActivity = CHECK((jclass)(env->CallObjectMethod(classLoaderInst, findClassMth, strClassName)));
auto funcMth = CHECK(env->GetMethodID(mPowderActivity, funcName, "()Ljava/lang/String;"));
auto resultRef = CHECK((jstring)env->CallObjectMethod(activityInst, funcMth));
Defer deleteStr([env, resultRef]() { env->DeleteLocalRef(resultRef); });
auto *resultBytes = CHECK(env->GetStringUTFChars(resultRef, 0));
Defer deleteUtf([env, resultRef, resultBytes]() { env->ReleaseStringUTFChars(resultRef, resultBytes); });
result = resultBytes;
}
catch (const CheckFailed &ex)
{
__android_log_print(ANDROID_LOG_ERROR, APPID, "CallActivityStringFunc/%s failed: %s", funcName, ex.what());
return std::nullopt;
}
#undef CHECK
return result;
}
ByteString DefaultDdir()
{
auto result = CallActivityStringFunc("getDefaultDdir");
if (result)
{
__android_log_print(ANDROID_LOG_ERROR, APPID, "DefaultDdir succeeded, data dir is %s", result->c_str());
return *result;
}
return "";
}
} }

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/String.h"
#include <optional>
namespace Platform
{
std::optional<ByteString> CallActivityStringFunc(const char *funcName);
}

View File

@@ -42,7 +42,6 @@ elif host_platform == 'android'
) )
powder_files += files( powder_files += files(
'MainCommon.cpp', 'MainCommon.cpp',
'DdirCommon.cpp',
) )
elif host_platform == 'emscripten' elif host_platform == 'emscripten'
use_bluescreen = false use_bluescreen = false