Skip to content

Commit a74b8d0

Browse files
authored
Implement support for the Storage Access Framework on Android (#18336)
* Add a work-in-progress Storage Access Framework backend to libretro-common/vfs * Fix compilation error in `retro_vfs_file_open_impl()` on Windows * Don't gate the contents of vfs_implementation_saf.h behind `HAVE_SAF` * Add a button to the file browser on Android for opening a directory picker * Fix a bug in `retro_vfs_path_split_saf()` * Don't include inaccessible directories in the file browser in Google Play Android builds * Fix missing return statement in `action_ok_open_picker()` * Improve the documentation for the SAF VFS backend in libretro-common * Implement opening the path in the file browser when selected using the SAF tree picker * Change indentation style in VfsImplementationSaf.java to 3 spaces * Check if `vfs_saf_get_jni_env` is null in vfs_implementation_saf.c before performing VFS operations * Fix stat function in SAF VFS backend * Don't gate the definition of `safTreeAdded()` behind `HAVE_SAF` * Fix memory leak when calling `retro_vfs_closedir_impl()` on a SAF directory * Fix `NullPointerException` in `SafStat` constructor * Create global references for all Java classes used by the SAF VFS backend * Improve the readdir implementation for the SAF VFS backend * Fix a bug in `retro_vfs_path_join_saf()` * Show the list of SAF trees that the app can access in the file browser on Android * Improve error handling in VfsImplementationSaf.java * Check if the directory already exists in the mkdir implementation in VfsImplementationSaf.java * Enforce a minimum Android API level of 21 in VfsImplementationSaf.java * Remove the comma at the end of `enum vfs_scheme` in libretro-common/include/vfs/vfs.h * Fix nonstandard C89 in libretro-common/vfs/vfs_implementation.c * Fix mkdir implementation in VfsImplementationSaf.java * Fix open implementation in VfsImplementationSaf.java not creating files that don't exist
1 parent 7e0d99f commit a74b8d0

13 files changed

Lines changed: 1805 additions & 138 deletions

File tree

Makefile.common

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ OBJ += \
302302
$(LIBRETRO_COMM_DIR)/streams/network_stream.o \
303303
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.o
304304

305+
ifeq ($(ANDROID), 1)
306+
OBJ += $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_saf.o
307+
endif
308+
305309
OBJ += \
306310
$(LIBRETRO_COMM_DIR)/lists/string_list.o \
307311
$(LIBRETRO_COMM_DIR)/string/stdstring.o \

frontend/drivers/platform_unix.c

Lines changed: 200 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757

5858
#ifdef ANDROID
5959
#include <sys/system_properties.h>
60+
#ifdef HAVE_SAF
61+
#include <vfs/vfs_implementation_saf.h>
62+
#include "../../menu/menu_cbs.h"
63+
#endif
6064
#endif
6165

6266
#if defined(DINGUX)
@@ -608,6 +612,64 @@ static void frontend_android_shutdown(bool unused)
608612
exit(0);
609613
}
610614

615+
#ifdef HAVE_SAF
616+
void android_show_saf_tree_picker(void)
617+
{
618+
JNIEnv *env;
619+
620+
if (!g_android || !g_android->have_saf)
621+
return;
622+
623+
env = jni_thread_getenv();
624+
if (!env)
625+
return;
626+
627+
CALL_VOID_METHOD(env, g_android->activity->clazz, g_android->requestOpenDocumentTree);
628+
}
629+
#endif
630+
631+
/*
632+
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
633+
* Method: safTreeAdded
634+
* Signature: (Ljava/lang/String;)V
635+
*/
636+
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_safTreeAdded
637+
(JNIEnv *env, jobject this_obj, jstring tree_obj)
638+
{
639+
#ifdef HAVE_SAF
640+
const char *tree;
641+
char *serialized_path;
642+
643+
tree = (*env)->GetStringUTFChars(env, tree_obj, NULL);
644+
if ((*env)->ExceptionOccurred(env))
645+
{
646+
(*env)->ExceptionDescribe(env);
647+
(*env)->ExceptionClear(env);
648+
return;
649+
}
650+
651+
if ((serialized_path = retro_vfs_path_join_saf(tree, "")) != NULL)
652+
{
653+
generic_action_ok_displaylist_push(
654+
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_PICKER),
655+
serialized_path,
656+
msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES),
657+
MENU_SETTING_ACTION,
658+
0,
659+
0,
660+
ACTION_OK_DL_CONTENT_LIST);
661+
free(serialized_path);
662+
}
663+
664+
(*env)->ReleaseStringUTFChars(env, tree_obj, tree);
665+
if ((*env)->ExceptionOccurred(env))
666+
{
667+
(*env)->ExceptionDescribe(env);
668+
(*env)->ExceptionClear(env);
669+
}
670+
#endif
671+
}
672+
611673
#elif !defined(DINGUX)
612674
static bool make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
613675
{
@@ -1973,6 +2035,11 @@ static void android_app_destroy(struct android_app *android_app)
19732035
CALL_VOID_METHOD(env, android_app->activity->clazz,
19742036
android_app->onRetroArchExit);
19752037

2038+
#ifdef HAVE_SAF
2039+
if (android_app->have_saf)
2040+
retro_vfs_deinit_saf();
2041+
#endif
2042+
19762043
if (android_app->inputQueue)
19772044
AInputQueue_detachLooper(android_app->inputQueue);
19782045

@@ -2135,6 +2202,18 @@ static void frontend_unix_init(void *data)
21352202
GET_METHOD_ID(env, android_app->accessibilitySpeak, class,
21362203
"accessibilitySpeak", "(Ljava/lang/String;)V");
21372204

2205+
CALL_BOOLEAN_METHOD(env, android_app->is_play_store_build, android_app->activity->clazz, android_app->isPlayStoreBuild)
2206+
2207+
#ifdef HAVE_SAF
2208+
GET_METHOD_ID(env, android_app->requestOpenDocumentTree, class,
2209+
"requestOpenDocumentTree", "()V");
2210+
2211+
GET_METHOD_ID(env, android_app->getPersistedSafTrees, class,
2212+
"getPersistedSafTrees", "()[Ljava/lang/String;");
2213+
2214+
android_app->have_saf = retro_vfs_init_saf(jni_thread_getenv, android_app->activity->clazz);
2215+
#endif
2216+
21382217
GET_OBJECT_CLASS(env, class, obj);
21392218
GET_METHOD_ID(env, android_app->getStringExtra, class,
21402219
"getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
@@ -2184,53 +2263,73 @@ static int frontend_unix_parse_drive_list(void *data, bool load_content)
21842263
CALL_OBJ_METHOD(env, obj, g_android->activity->clazz,
21852264
g_android->getIntent);
21862265

2187-
if (g_android->getVolumeCount)
2266+
if (!g_android->is_play_store_build && g_android->getVolumeCount)
21882267
{
21892268
CALL_INT_METHOD(env, output,
21902269
g_android->activity->clazz, g_android->getVolumeCount);
21912270
volume_count = output;
21922271
}
21932272

2194-
if (!string_is_empty(internal_storage_path))
2273+
if (!g_android->is_play_store_build)
21952274
{
2196-
if (storage_permissions == INTERNAL_STORAGE_WRITABLE)
2275+
if (!string_is_empty(internal_storage_path))
21972276
{
2198-
char user_data_path[PATH_MAX_LENGTH];
2199-
fill_pathname_join_special(user_data_path,
2200-
internal_storage_path, "RetroArch",
2201-
sizeof(user_data_path));
2277+
if (storage_permissions == INTERNAL_STORAGE_WRITABLE)
2278+
{
2279+
char user_data_path[PATH_MAX_LENGTH];
2280+
fill_pathname_join_special(user_data_path,
2281+
internal_storage_path, "RetroArch",
2282+
sizeof(user_data_path));
2283+
2284+
menu_entries_append(list,
2285+
user_data_path,
2286+
msg_hash_to_str(MSG_INTERNAL_STORAGE),
2287+
enum_idx,
2288+
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2289+
}
22022290

22032291
menu_entries_append(list,
2204-
user_data_path,
2292+
internal_storage_path,
22052293
msg_hash_to_str(MSG_INTERNAL_STORAGE),
22062294
enum_idx,
22072295
FILE_TYPE_DIRECTORY, 0, 0, NULL);
22082296
}
2209-
2210-
menu_entries_append(list,
2211-
internal_storage_path,
2212-
msg_hash_to_str(MSG_INTERNAL_STORAGE),
2213-
enum_idx,
2214-
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2297+
else
2298+
menu_entries_append(list,
2299+
"/storage/emulated/0",
2300+
msg_hash_to_str(MSG_REMOVABLE_STORAGE),
2301+
enum_idx,
2302+
FILE_TYPE_DIRECTORY, 0, 0, NULL);
22152303
}
2216-
else
2304+
2305+
if (!g_android->is_play_store_build)
22172306
menu_entries_append(list,
2218-
"/storage/emulated/0",
2307+
"/storage",
22192308
msg_hash_to_str(MSG_REMOVABLE_STORAGE),
22202309
enum_idx,
22212310
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2222-
2223-
menu_entries_append(list,
2224-
"/storage",
2225-
msg_hash_to_str(MSG_REMOVABLE_STORAGE),
2226-
enum_idx,
2227-
FILE_TYPE_DIRECTORY, 0, 0, NULL);
22282311
if (!string_is_empty(internal_storage_app_path))
2312+
{
2313+
if (g_android->is_play_store_build)
2314+
{
2315+
char user_data_app_path[PATH_MAX_LENGTH];
2316+
fill_pathname_join_special(user_data_app_path,
2317+
internal_storage_app_path, "RetroArch",
2318+
sizeof(user_data_app_path));
2319+
2320+
menu_entries_append(list,
2321+
user_data_app_path,
2322+
msg_hash_to_str(MSG_EXTERNAL_APPLICATION_DIR),
2323+
enum_idx,
2324+
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2325+
}
2326+
22292327
menu_entries_append(list,
22302328
internal_storage_app_path,
22312329
msg_hash_to_str(MSG_EXTERNAL_APPLICATION_DIR),
22322330
enum_idx,
22332331
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2332+
}
22342333
if (!string_is_empty(app_dir))
22352334
menu_entries_append(list,
22362335
app_dir,
@@ -2266,8 +2365,8 @@ static int frontend_unix_parse_drive_list(void *data, bool load_content)
22662365
enum_idx,
22672366
FILE_TYPE_DIRECTORY, 0, 0, NULL);
22682367
}
2269-
22702368
}
2369+
22712370
#elif defined(WEBOS)
22722371
if (path_is_directory("/media/internal"))
22732372
menu_entries_append(list, "/media/internal",
@@ -2349,10 +2448,84 @@ static int frontend_unix_parse_drive_list(void *data, bool load_content)
23492448
}
23502449
#endif
23512450

2352-
menu_entries_append(list, "/",
2353-
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
2354-
enum_idx,
2355-
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2451+
#ifdef ANDROID
2452+
if (!g_android->is_play_store_build)
2453+
#else
2454+
if (1)
2455+
#endif
2456+
{
2457+
menu_entries_append(list, "/",
2458+
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
2459+
enum_idx,
2460+
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2461+
}
2462+
2463+
#if defined(ANDROID) && defined(HAVE_SAF)
2464+
if (g_android->have_saf)
2465+
{
2466+
JNIEnv *env = jni_thread_getenv();
2467+
2468+
if (env != NULL)
2469+
{
2470+
jarray trees = (*env)->CallObjectMethod(env, g_android->activity->clazz, g_android->getPersistedSafTrees);
2471+
if ((*env)->ExceptionOccurred(env))
2472+
{
2473+
(*env)->ExceptionDescribe(env);
2474+
(*env)->ExceptionClear(env);
2475+
}
2476+
else
2477+
{
2478+
jsize trees_length = (*env)->GetArrayLength(env, trees);
2479+
if ((*env)->ExceptionOccurred(env))
2480+
{
2481+
(*env)->ExceptionDescribe(env);
2482+
(*env)->ExceptionClear(env);
2483+
}
2484+
else
2485+
for (jsize i = 0; i < trees_length; ++i)
2486+
{
2487+
const char *tree_chars;
2488+
char *serialized_path;
2489+
jstring tree = (*env)->GetObjectArrayElement(env, trees, i);
2490+
if ((*env)->ExceptionOccurred(env))
2491+
{
2492+
(*env)->ExceptionDescribe(env);
2493+
(*env)->ExceptionClear(env);
2494+
continue;
2495+
}
2496+
tree_chars = (*env)->GetStringUTFChars(env, tree, NULL);
2497+
if ((*env)->ExceptionOccurred(env))
2498+
{
2499+
(*env)->ExceptionDescribe(env);
2500+
(*env)->ExceptionClear(env);
2501+
continue;
2502+
}
2503+
if ((serialized_path = retro_vfs_path_join_saf(tree_chars, "")) != NULL)
2504+
{
2505+
menu_entries_append(list,
2506+
serialized_path,
2507+
msg_hash_to_str(MSG_REMOVABLE_STORAGE),
2508+
enum_idx,
2509+
FILE_TYPE_DIRECTORY, 0, 0, NULL);
2510+
free(serialized_path);
2511+
}
2512+
(*env)->ReleaseStringUTFChars(env, tree, tree_chars);
2513+
if ((*env)->ExceptionOccurred(env))
2514+
{
2515+
(*env)->ExceptionDescribe(env);
2516+
(*env)->ExceptionClear(env);
2517+
}
2518+
}
2519+
}
2520+
}
2521+
2522+
menu_entries_append(list,
2523+
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_PICKER),
2524+
msg_hash_to_str(MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER),
2525+
MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER,
2526+
MENU_SETTING_ACTION, 0, 0, NULL);
2527+
}
2528+
#endif
23562529
#endif
23572530

23582531
return 0;

frontend/drivers/platform_unix.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ struct android_app
192192
uint16_t rumble_last_strength_weak[MAX_USERS];
193193
uint16_t rumble_last_strength[MAX_USERS];
194194
int id[MAX_USERS];
195+
196+
bool is_play_store_build;
197+
198+
#ifdef HAVE_SAF
199+
jmethodID requestOpenDocumentTree;
200+
jmethodID getPersistedSafTrees;
201+
bool have_saf;
202+
#endif
195203
};
196204

197205
enum
@@ -369,6 +377,10 @@ extern struct android_app *g_android;
369377

370378
bool is_screen_reader_enabled(void);
371379

380+
#ifdef HAVE_SAF
381+
void android_show_saf_tree_picker(void);
382+
#endif
383+
372384
#endif
373385

374386
#endif

griffin/griffin.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,10 @@ FILE
10821082
#include "../libretro-common/media/media_detect_cd.c"
10831083
#endif
10841084

1085+
#ifdef ANDROID
1086+
#include "../libretro-common/vfs/vfs_implementation_saf.c"
1087+
#endif
1088+
10851089
#include "../libretro-common/string/stdstring.c"
10861090
#include "../libretro-common/file/nbio/nbio_stdio.c"
10871091
#if defined(__linux__)

libretro-common/include/vfs/vfs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ typedef struct
5959
enum vfs_scheme
6060
{
6161
VFS_SCHEME_NONE = 0,
62-
VFS_SCHEME_CDROM
62+
VFS_SCHEME_CDROM,
63+
VFS_SCHEME_SAF
6364
};
6465

6566
#if !(defined(__WINRT__) && defined(__cplusplus_winrt))

0 commit comments

Comments
 (0)