diff --git a/src/common/android.cpp b/src/common/android.cpp index 105ac31be..629ff0b1b 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -126,9 +126,6 @@ bool openURL(const std::string &url) static jmethodID openURL = env->GetMethodID(clazz, "openURLFromLOVE", "(Ljava/lang/String;)Z"); - if (openURL == nullptr) - return false; - jstring jstringURL = env->NewStringUTF(url.c_str()); jboolean result = env->CallBooleanMethod(clazz, openURL, jstringURL); @@ -725,9 +722,9 @@ const char *getCRequirePath() { JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); - jclass clazz(env->GetObjectClass(activity)); - jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); + static jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;"); jstring cpath = (jstring) env->CallObjectMethod(activity, getCRequireMethod); const char *utf = env->GetStringUTFChars(cpath, nullptr); @@ -745,6 +742,29 @@ const char *getCRequirePath() return path.c_str(); } +int getFDFromContentProtocol(const char *path) +{ + int fd = -1; + + if (strstr(path, "content://") == path) + { + JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); + jobject activity = (jobject) SDL_AndroidGetActivity(); + jclass clazz = env->GetObjectClass(activity); + + static jmethodID converter = env->GetMethodID(clazz, "convertToFileDescriptor", "(Ljava/lang/String;)I"); + + jstring uri = env->NewStringUTF(path); + fd = (int) env->CallIntMethod(activity, converter, uri); + + env->DeleteLocalRef(uri); + env->DeleteLocalRef(clazz); + env->DeleteLocalRef(activity); + } + + return fd; +} + int getFDFromLoveProtocol(const char *path) { constexpr const char PROTOCOL[] = "love2d://fd/"; diff --git a/src/common/android.h b/src/common/android.h index 25eb1621e..24f32a27a 100644 --- a/src/common/android.h +++ b/src/common/android.h @@ -96,6 +96,13 @@ bool checkFusedGame(void **physfsIO_Out); const char *getCRequirePath(); +/** + * Convert "content://" to file descriptor. + * @param path Path with content:// URI + * @return File descriptor if successful, -1 on failure. + */ +int getFDFromContentProtocol(const char *path); + /** * Attempt to parse "(/)love2d://fd/" from path. * @param path Potentially special path. diff --git a/src/modules/filesystem/NativeFile.cpp b/src/modules/filesystem/NativeFile.cpp index 58753cfcf..d0640da52 100644 --- a/src/modules/filesystem/NativeFile.cpp +++ b/src/modules/filesystem/NativeFile.cpp @@ -22,6 +22,10 @@ #include "NativeFile.h" #include "common/utf8.h" +#ifdef LOVE_ANDROID +#include "common/android.h" +#endif + // Assume POSIX or Visual Studio. #include #include @@ -84,7 +88,22 @@ bool NativeFile::open(Mode newmode) if (file != nullptr) return false; -#ifdef LOVE_WINDOWS +#if defined(LOVE_ANDROID) + // Try to handle content:// URI + int fd = love::android::getFDFromContentProtocol(filename.c_str()); + if (fd != -1) + { + if (newmode != MODE_READ) + { + ::close(fd); + throw love::Exception("%s is read-only.", filename.c_str()); + } + + file = fdopen(fd, "rb"); + } + else + file = fopen(filename.c_str(), getModeString(newmode)); +#elif defined(LOVE_WINDOWS) // make sure non-ASCII filenames work. std::wstring modestr = to_widestr(getModeString(newmode)); std::wstring wfilename = to_widestr(filename);