11

As Android is very inconsistent between different major Versions regarding File access, I feel a bit lost. I try to describe the problem as easy as possible:

My Company uses a commercial native DRM to protect other native library's we provide. We have a Licensing App, which invoked some Voodoo to end up with Licensing files in say /sdcard/companyname/LicenseContainer/. Other protected Apps looked at this directory in native code, checking if the user has a valid License.

The Android 10 update however, invalidated this workflow completely as it only provides scoped storage access. We could do a workaround using Storage Manager to grant access, which is unfortunately also deprecated now.

So my Question is now: How can one App save files to a location on /sdcard/FOLDER which are

  1. not deleted on App deletion
  2. Accessible in native code by other apps

I'm a bit overwhelmed with all the possible solutions (SAF, FileProvider, etc), which invoke often that one app grants permissions to the other. But the files should be accessible without an installed first app who put it there.

I know there must be a solution, as recent FileManagers (i.e. Files by Google) get access to the whole /sdcard/ directory.

Whats the easiest, future-proof route to go here without invoking "hacks" like android:requestLegacyExternalStorage="true"

Rafael T
  • 15,401
  • 15
  • 83
  • 144
  • Did you find an answer, if so, can you please share? I'm dealing with the exact same scenario. – ClassA Feb 08 '20 at 15:31
  • @ClassA If you are dealing with native code, your only option is to pass `FileDescriptors` as described here https://developer.android.com/training/data-storage/shared/media#native – Rafael T Feb 11 '20 at 12:27

2 Answers2

5

You may ask the user to give you access to any file or directory, including the root of internal storage or external SD card. You can make this access permanent for your app, be able to read/write files anywhere with the Scoped Storage API afterwards, until the app is uninstalled or reset.

Then, if you need to read or write a file in native C/C++ code, you may get Linux file descriptor (int number) of the file and pass it to native code to use with fdopen() call for example.

Here is a Java code snippet to get a file descriptor form a single file Uri (which in string form is like content://...)

    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r"); // gets FileNotFoundException here, if file we used to have was deleted
    int fd = parcelFileDescriptor.getFd(); // detachFd() if we want to close in native code

If you have source code for your native libraries, or can call them with C FILE* - it will work fine. The only problem is when you don't have the source code and they expect a file path/name. * UPDATE *: it is still possible to use the path/file name strings to pass to C/C++ functions that expect a file name. Simply instead of the "real path/file name", create a name to symbolic link like this:

// fd is file descriptor obtained in Java code above
char fileName[32];
sprintf(fileName, "/proc/self/fd/%d", fd);
// The above fileName can be passed to C/C++ functions that expect a file name.
// They can read or write to it, depending on permissions to fd given in Java,
// but I guess C/C++ code can not create a new file. Someone correct me,
// if I'm mistaken here.

However, at this time I'm not sure that when you create a file in a directory beyond the app "sandbox" in that way, if the system will delete this file too after uninstall... Would need to write a quick test on Android 10 to find out, and then we still won't know if Google won't change this behavior in future.

gregko
  • 5,642
  • 9
  • 49
  • 76
  • All your threads on this Scoped Storage have been very helpful, thank you. I know this trick won't work with removable storage, but for now, I think there's no other choice but this. What I want to make sure is, does this always work with Android 10? On Android 11, it will, but not sure about 10 because direct *File path API* was introduced with Android 11 but not 10. – Jenix Dec 11 '21 at 12:28
  • Have you tested this code with Android 10 in *Scoped Storage* mode? (I mean, without setting the `requestLegacyExternalStorage` flag to true) Depending on your answer, I will decide whether I set the `requestLegacyExternalStorage` flag for Android 10 or not. (And keep using the legacy code on Android 10 and below) – Jenix Dec 11 '21 at 12:42
  • This code _probably_ still works on Android 11 and higher, but I don't use it much. I obtained exemption to use "All files access" for my app on Android 11+ from Google, but if the user refuses it, the app still works - I simply use Scope Storage to copy the file from elsewhere to the app private space, if real File operations are needed in Java or C/C++. Similarly if write access is needed, I create/write the file in app private storage, then copy it to the final destination using Scoped storage... – gregko Dec 11 '21 at 14:28
  • I don't know if they copied you, but many developers used this trick in their apps and libs for Android 11+ devices. So I think it's safe if no user selects a file from removable storage. But my question was about Android 10 that has no support for direct path access. From FAQ: "On Android 10, apps in the scoped storage environment cannot access files using the file path." – Jenix Dec 11 '21 at 16:18
  • "...Based on your feedback on the needs to work with existing native code or libraries, Android 11 now supports file path access for apps in scoped storage." Thanks to developers like you, they came back with the direct file path support, but it wasn't there on Android 10. That's why I'm asking. – Jenix Dec 11 '21 at 16:20
0

If you want to save files in shared storage (where it can be accessed by users & other apps) you need to use

  • for Media Files (Images, Videos, Audio, Downloads) use MediaStore
  • for Documents and Other Files use Storage Access Framework (this is simply a system file picker)

For instance you can use the following snippet to save a pdf file using Storage Access Framework

const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

After the user has picked a directory we still need to handle the result Uri in on onActivityResult method.

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // save your data using the `uri` path
        }
    }
}

You can read about this in more detail on the following blogpost

https://androidexplained.github.io/android/android11/scoped-storage/2020/09/29/file-saving-android-11.html

John Smith
  • 844
  • 8
  • 26
  • Does not explain how File Explorer Apps are able to create Folders in /sdcard/ or access Files basically evereywhere – Rafael T Oct 30 '20 at 09:13