Android public file storage paths

Is it possible for SDL2 to supply a public storage path accessable by the user for public files?

My app needs a file storage location that the user can access using Android’s own file manager, but the ones SDL2 supplies are all blocked by the OS from being accessed, other than by connecting to a PC to access it.

I managed to work around it a bit by checking if the path is at /Android/data/(appfolder), taking those subfolders off to replace that with /(appnamehere)/redirect.txt to redirect it somewhere else (the file contents are the new path), in my case it’s specifying a files folder in that very same directory (as an absolute path, although a relative path isn’t supported by my app yet).

Reference of bug tracker: Android app public shared files? · Issue #7592 · libsdl-org/SDL · GitHub

Does anyone else have a better, future-proof, way of handling this on Android?

Have a look at SDL_AndroidGetExternalStoragePath()

Well, that path I mentioned at /Android/data/(appfolder) is exactly what’s returned by SDL_AndroidGetExternalStoragePath().
The folder is only accessable from PC (using MTP) and the app itself, but inaccessable by the user using normal file managers on the device itself (which is the main issue here, as it can’t be used to redirect anywhere else more accessible).
The SDL_AndroidGetInternalStoragePath() is even worse, pointing to /data/…, which is even more private and inaccessable by both the file manager and the PC.

The only way to currently get around this (afaik) is simply letting the user itself decide where to put the files using a plain text file to redirect it to anywhere the user likes (in my case at SDL_AndroidGetExternalStoragePath with “Android/data/(appnamehere)” stripped off and “(appname)/redirect.txt” appended to that resulting string for a file path containing the path the user wants to use).
Then I still had to add some extra functionality for ease of use (support for absolute and relative paths that are specified in said file, by examining the contents for URL specifiers (alphanumeric followed by “:/”), single character with :\ for Windows (e.g. C:) and special absolute paths for other platforms (“flash*:/” and ms0:/ on PSP for internal flash storage). Anything else being used as a relative path to be appended to the path of the redirect.txt file instead of replacing said path.

byte is_pathseperator(char c)
{
	if (c == '/') //First case?
	{
		return 1; //First case!
	}
	if (c == '\\') //Second case?
	{
		return 2; //Second case!
	}
	return 0; //Not a path seperator!
}

byte is_textcharacter(char c)
{
	if ((c >= 'a') && (c <= 'z')) return 1; //Text!
	if ((c >= 'A') && (c <= 'Z')) return 1; //Text!
	if ((c >= '0') && (c <= '9')) return 1; //Text!
	switch (c) //Remaining cases?
	{
	case '~':
	case '`':
	case '!':
	case '@':
	case '#':
	case '$':
	case '%':
	case '^':
	case '&':
	case '*':
	case '(':
	case ')':
	case '-':
	case '_':
	case '+':
	case '=':
	case '{':
	case '}':
	case '[':
	case ']':
	case '|':
	case '\\':
	case ':':
	case ';':
	case '"':
	case '\'':
	case '<':
	case ',':
	case '>':
	case '.':
	case '?':
		return 2; //Valid text for a filename to end in!
	default:
		break;
	}
	return 0; //Not a text character!
}

//result: -1:invalid, 0:Relative, 1:absolute, 2:URL
sbyte is_absolutepath(char* path)
{
	size_t len;
	#ifdef IS_PSP
	size_t len2;
	char internalstorage1identifier[6] = "flash";
	char internalstorage2identifier[6] = "ms0:/";
	#endif
	char URIidentifier[3] = ":/";
	#ifdef IS_WINDOWS
	//Windows drive identifier!
	char driveidentifier[3] = ":\\";
	#endif

	byte URIidentifier_len;
	#ifdef IS_WINDOWS
	byte driveidentifier_len;
	#endif
	char* p;
	if (!path) return -1; //Nothing!
	if (!*path) return -1; //Invalid path!
	#if !defined(IS_WINDOWS) && !defined(IS_PSP)
	if (is_pathseperator(*path)) //Starts with a path seperator?
	{
		return 1; //Plain absolute path!
	}
	#else
	//Windows and PSP don't have linux-style root paths?
	if (is_pathseperator(*path)) //Starts with a path seperator?
	{
		if (is_pathseperator(*path) == 1) //Invalid to use on Windows?
		{
			return -1; //Invalid path!
		}
		return 1; //Plain absolute path!
	}
	#endif
	//Now, check for URI header!
	p = path; //Init!
	len = strlen(path); //Length of the string!
	#ifdef IS_PSP
	len2 = len; //Copy to check!
	#endif
	URIidentifier_len = safe_strlen(URIidentifier, sizeof(URIidentifier)); //Length to check against!
	#ifdef IS_WINDOWS
	driveidentifier_len = safe_strlen(driveidentifier, sizeof(driveidentifier)); //Length to check against!
	#endif
	for (; *p; ++p, --len) //Check until EOS!
	{
		if ((p != path) && (len >= URIidentifier_len)) //Might start an URI?
		{
			if (memcmp(p, &URIidentifier,URIidentifier_len)==0) //Matched?
			{
				#ifdef IS_PSP
				if (len2 >= safe_strlen(internalstorage1identifier, sizeof(internalstorage1identifier))) //Check #1!
				{
					if (memcmp(path, &internalstorage1identifier, safe_strlen(internalstorage1identifier, sizeof(internalstorage1identifier))) == 0) //Matched?
					{
						return 1; //Absolute drive path!
					}
				}
				if (len2 >= safe_strlen(internalstorage2identifier, sizeof(internalstorage2identifier))) //Check #1!
				{
					if (memcmp(path, &internalstorage2identifier, safe_strlen(internalstorage2identifier, sizeof(internalstorage2identifier))) == 0) //Matched?
					{
						return 1; //Absolute drive path!
					}
				}
				#endif
				//Found an proper URL header!
				return 2; //Absolute URL path!
			}
			//Otherwise, not matched yet, check for other options!
		}
		#ifdef IS_WINDOWS
		//Windows absolute path?
		if ((p == (path + 1)) && (len >= driveidentifier_len)) //Might start an URI?
		{
			if (memcmp(p, &driveidentifier, driveidentifier_len) == 0) //Matched?
			{
				//Found an proper absolute drive path header!
				return 1; //Absolute drive path!
			}
			//Otherwise, not matched yet, check for other options!
		}
		#endif
		if (is_textcharacter(*p) != 1) //Non-text is invalid for URL header?
		{
			break; //Abort search: invalid header!
		}
	}
	return 0; //Relative path!
}

Android/data/app/files/ is where all the public-accessable files are kept. It works for me and I can access the files there with a file browser.

Here’s an example of the popular app, Telegram, saving files here, and me accessing them from a file manager.

Here is the same example on an ancient Nexas 7. No need to over-complicate anything. Just use SDL_AndroidGetExternalStoragePath() as your storage path. Job done.

Well, on the most recent Android versions said folder is blocked by the OS. The entire Android/data folder is in fact (nothing in there is visible to the user, except when using a MTP connection to the device).

That’s using the built-in file manager (Samsung’s in my case, on a A42s device), using it’s My Files app that’s pre-installed onto the Android device.

Official Android specs also mention it’s only accessable by the app itself and through MTP:
I’m on Android 13 myself, but apparently it’s been an issue since Android 11: Reddit - Dive into anything

My Nokia 8 is on Android 12 though, and I can access that folder form 3rd party file browsers.

Are Google pushing for developers to use the share feature to share files, rather than let users access an app’s shared file space maybe?

Sorry to bump this topic, but I’m now starting to receive complaints from my users about this exact issue. Like you, on my device (a OnePlus 10 Pro running Android 13) I can see the folder returned from SDL_AndroidGetExternalStoragePath() from a 3rd party File Manager, but evidently that is not the case for other people.

So I agree with the OP that a way of finding a path to a folder that can be accessed from the File Manager, on all devices and in the latest version of Android, is required. Apparently the way this has been tackled in some apps is to create a sub-folder in Documents, but I don’t know how to do that.