diff --git a/android-project/app/proguard-rules.pro b/android-project/app/proguard-rules.pro index 0cb79dc8e5..b608d09b1a 100644 --- a/android-project/app/proguard-rules.pro +++ b/android-project/app/proguard-rules.pro @@ -48,6 +48,7 @@ int openURL(java.lang.String); int showToast(java.lang.String, int, int, int, int); native java.lang.String nativeGetHint(java.lang.String); + int openFileDescriptor(java.lang.String, java.lang.String); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index a68eb603ec..a24ded14cc 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -23,6 +23,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; @@ -44,6 +45,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; +import java.io.FileNotFoundException; import java.util.Hashtable; import java.util.Locale; @@ -1938,6 +1940,23 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } return 0; } + + /** + * This method is called by SDL using JNI. + */ + public static int openFileDescriptor(String uri, String mode) throws Exception { + if (mSingleton == null) { + return -1; + } + + try { + ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode); + return pfd != null ? pfd.detachFd() : -1; + } catch (FileNotFoundException e) { + e.printStackTrace(); + return -1; + } + } } /** diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index d3f5c3e587..4407e09ff3 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -175,8 +175,9 @@ typedef struct SDL_IOStream SDL_IOStream; * This function supports Unicode filenames, but they must be encoded in UTF-8 * format, regardless of the underlying operating system. * - * As a fallback, SDL_IOFromFile() will transparently open a matching filename - * in an Android app's `assets`. + * In Android, SDL_IOFromFile() can be used to open content:// URIs. As a + * fallback, SDL_IOFromFile() will transparently open a matching filename + * in the app's `assets`. * * Closing the SDL_IOStream will close SDL's internal file handle. * diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 01e03bde60..4929039607 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -345,6 +345,7 @@ static jmethodID midSetWindowStyle; static jmethodID midShouldMinimizeOnFocusLoss; static jmethodID midShowTextInput; static jmethodID midSupportsRelativeMouse; +static jmethodID midOpenFileDescriptor; /* audio manager */ static jclass mAudioManagerClass; @@ -638,6 +639,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss", "()Z"); midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z"); midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z"); + midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); if (!midClipboardGetText || !midClipboardHasText || @@ -667,7 +669,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midSetWindowStyle || !midShouldMinimizeOnFocusLoss || !midShowTextInput || - !midSupportsRelativeMouse) { + !midSupportsRelativeMouse || + !midOpenFileDescriptor) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); } @@ -2766,4 +2769,58 @@ int Android_JNI_OpenURL(const char *url) return ret; } +int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode) +{ + /* Get fopen-style modes */ + int moderead = 0, modewrite = 0, modeappend = 0, modeupdate = 0; + + for (const char *cmode = mode; *cmode; cmode++) { + switch (*cmode) { + case 'a': + modeappend = 1; + break; + case 'r': + moderead = 1; + break; + case 'w': + modewrite = 1; + break; + case '+': + modeupdate = 1; + break; + default: + break; + } + } + + /* Translate fopen-style modes to ContentResolver modes. */ + /* Android only allows "r", "w", "wt", "wa", "rw" or "rwt". */ + const char *contentResolverMode = "r"; + + if (moderead) { + if (modewrite) { + contentResolverMode = "rwt"; + } else { + contentResolverMode = modeupdate ? "rw" : "r"; + } + } else if (modewrite) { + contentResolverMode = modeupdate ? "rwt" : "wt"; + } else if (modeappend) { + contentResolverMode = modeupdate ? "rw" : "wa"; + } + + JNIEnv *env = Android_JNI_GetEnv(); + jstring jstringUri = (*env)->NewStringUTF(env, uri); + jstring jstringMode = (*env)->NewStringUTF(env, contentResolverMode); + jint fd = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenFileDescriptor, jstringUri, jstringMode); + (*env)->DeleteLocalRef(env, jstringUri); + (*env)->DeleteLocalRef(env, jstringMode); + + if (fd == -1) { + SDL_SetError("Unspecified error in JNI"); + } + + return fd; +} + #endif /* SDL_PLATFORM_ANDROID */ diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index b323722e27..3ad8901430 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -75,6 +75,7 @@ int Android_JNI_FileClose(void *userdata); /* Environment support */ void Android_JNI_GetManifestEnvironmentVariables(void); +int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode); /* Clipboard support */ int Android_JNI_SetClipboardText(const char *text); diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c index d261094f36..5edc73fef3 100644 --- a/src/file/SDL_iostream.c +++ b/src/file/SDL_iostream.c @@ -54,6 +54,7 @@ struct SDL_IOStream #endif /* SDL_PLATFORM_3DS */ #ifdef SDL_PLATFORM_ANDROID +#include #include "../core/android/SDL_android.h" #endif @@ -558,6 +559,22 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } return SDL_IOFromFP(fp, 1); } + } else if (SDL_strncmp(file, "content://", 10) == 0) { + /* Try opening content:// URI */ + int fd = Android_JNI_OpenFileDescriptor(file, mode); + if (fd == -1) { + /* SDL error is already set. */ + return NULL; + } + + FILE *fp = fdopen(fd, mode); + if (!fp) { + close(fd); + SDL_SetError("Unable to open file descriptor (%d) from URI %s", fd, file); + return NULL; + } + + return SDL_IOFromFP(fp, SDL_TRUE); } else { /* Try opening it from internal storage if it's a relative path */ // !!! FIXME: why not just "char path[PATH_MAX];"