Edit: I revised this answer to include sample approach code, which I originally called "writing specialized ContentProvider". This should fully satisfy the requirements of this issue. It probably makes the answer too big, but now it has internal code dependencies, so let's leave it whole. The main thing remains the same: use ContentPrvder below if you want, but try to provide file:// Uris to applications that support them if you do not want to be blamed for the application crashing.
Original answer
I would stay away from the Storage Access Framework as it is now. It is not well supported by Google, and the support in the applications is terrible, therefore it is difficult to tell between the errors in these applications and SAF itself. If you are pretty sure (which actually means βit's better to use a try-catch block better than the average Android developerβ), use the Storage Access Framework yourself, but pass only the good paths file:// to others.
You can use the following trick to get the path to the file system from ParcelFileDescriptor (you can get it from the ContentResolver by calling openFileDescriptor ):
class FdCompat { public static String getFdPath(ParcelFileDescriptor fd) { final String resolved; try { final File procfsFdFile = new File("/proc/self/fd/" + fd.getFd()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Returned name may be empty or "pipe:", "socket:", "(deleted)" etc. resolved = Os.readlink(procfsFdFile.getAbsolutePath()); } else { // Returned name is usually valid or empty, but may start from // funny prefix if the file does not have a name resolved = procfsFdFile.getCanonicalPath(); } if (TextUtils.isEmpty(resolved) || resolved.charAt(0) != '/' || resolved.startsWith("/proc/") || resolved.startsWith("/fd/")) return null; } catch (IOException ioe) { // This exception means, that given file DID have some name, but it is // too long, some of symlinks in the path were broken or, most // likely, one of it directories is inaccessible for reading. // Either way, it is almost certainly not a pipe. return ""; } catch (Exception errnoe) { // Actually ErrnoException, but base type avoids VerifyError on old versions // This exception should be VERY rare and means, that the descriptor // was made unavailable by some Unix magic. return null; } return resolved; } }
You should be prepared for the fact that the above method will return null (a file is a pipe or socket, which is completely legal) or an empty path (there is no read access to the parent directory of files). If this happens, copy the entire stream to a directory that you can access.
Complete solution
If you really want to stick with the Uris content provider, here you go. Below is the code for ContentProvider. Paste into your application (and register it in AndroidManifest). Use the getShareableUri method below to convert the resulting Uri storage access infrastructure to your own. Transfer this Uri to other applications instead of the original Uri.
The code below is unsafe (you can easily make it safe, but explaining that it will extend the length of this answer beyond imagination). If you don't care, use file:// Uris-Linux file systems are widely considered quite safe.
The extension of the solution below to provide arbitrary file descriptors without a corresponding Uri remains as an exercise for reading.
public class FdProvider extends ContentProvider { private static final String ORIGINAL_URI = "o"; private static final String FD = "fd"; private static final String PATH = "p"; private static final Uri BASE_URI = Uri.parse("content://com.example.fdhelper/");