# SDL: Android: Allow SDL\_IOFromFile to open content:// URI. (#9696)

**URL:** <https://discourse.libsdl.org/t/sdl-android-allow-sdl-iofromfile-to-open-content-uri-9696/50931>\
**Category:** SDL Commits\
**Created:** [May 6, 2024, 4:06pm UTC](https://discourse.libsdl.org/t/sdl-android-allow-sdl-iofromfile-to-open-content-uri-9696/50931 "2024-05-06T16:06:02Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [May 6, 2024, 4:06pm UTC](https://discourse.libsdl.org/t/sdl-android-allow-sdl-iofromfile-to-open-content-uri-9696/50931/1 "2024-05-06T16:06:03Z")

</div>

> <https://github.com/libsdl-org/SDL/commit/33ae7e38d6dc5d4a7ec151016ce983abe297b9fa>

```diff
From 33ae7e38d6dc5d4a7ec151016ce983abe297b9fa Mon Sep 17 00:00:00 2001
From: Miku AuahDark <[EMAIL REDACTED]>
Date: Tue, 7 May 2024 00:05:49 +0800
Subject: [PATCH] Android: Allow SDL_IOFromFile to open content:// URI. (#9696)

---
 android-project/app/proguard-rules.pro | 1 +
 .../main/java/org/libsdl/app/SDLActivity.java | 19 ++++++
 include/SDL3/SDL_iostream.h | 5 +-
 src/core/android/SDL_android.c | 59 ++++++++++++++++++-
 src/core/android/SDL_android.h | 1 +
 src/file/SDL_iostream.c | 17 ++++++
 6 files changed, 99 insertions(+), 3 deletions(-)

diff --git a/android-project/app/proguard-rules.pro b/android-project/app/proguard-rules.pro
index 0cb79dc8e592b..b608d09b1afae 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 a68eb603ec95f..a24ded14ccdae 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.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.TextView;
 import android.widget.Toast;
 
+import java.io.FileNotFoundException;
 import java.util.Hashtable;
 import java.util.Locale;
 
@@ -1938,6 +1940,23 @@ public void run() {
         }
         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 d3f5c3e587cd1..4407e09ff38e8 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 01e03bde60f70..4929039607f61 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 b323722e27d10..3ad8901430f34 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 d261094f36a35..5edc73fef342e 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 <unistd.h>
 #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];"

```
