Android: open the file with the choice of intentions from the URI obtained by the storage access platform

First, the user can select files with a new repository access platform (assuming the API application> 19):

https://developer.android.com/guide/topics/providers/document-provider.html

Then I keep the links to these selected files, saving the URI characters that look like this:

content://com.android.providers.downloads.documments/document/745 

(in this case, the file is from the default download file).

Later I want the user to open these files (for example, the names displayed in the list of the user interface, and the user selects one).

I want to do this with the Android Favorite Select function, and all I have is the above URI ...

Thank,

+11
android android-intent android-file storage-access-framework android-afilechooser
May 30 '15 at 12:53
source share
2 answers

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/"); // Create an Uri from some other Uri and (optionally) corresponding // file descriptor (if you don't plan to close it until your process is dead). public static Uri getShareableUri(@Nullable ParcelFileDescriptor fd, Uri trueUri) { String path = fd == null ? null : FdCompat.getFdPath(fd); String uri = trueUri.toString(); Uri.Builder builder = BASE_URI.buildUpon(); if (!TextUtils.isEmpty(uri)) builder.appendQueryParameter(ORIGINAL_URI, uri); if (fd != null && !TextUtils.isEmpty(path)) builder.appendQueryParameter(FD, String.valueOf(fd.getFd())) .appendQueryParameter(PATH, path); return builder.build(); } public boolean onCreate() { return true; } public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { String o = uri.getQueryParameter(ORIGINAL_URI); String fd = uri.getQueryParameter(FD); String path = uri.getQueryParameter(PATH); if (TextUtils.isEmpty(o)) return null; // offer the descriptor directly, if our process still has it try { if (!TextUtils.isEmpty(fd) && !TextUtils.isEmpty(path)) { int intFd = Integer.parseInt(fd); ParcelFileDescriptor desc = ParcelFileDescriptor.fromFd(intFd); if (intFd >= 0 && path.equals(FdCompat.getFdPath(desc))) { return desc; } } } catch (RuntimeException | IOException ignore) {} // otherwise just forward the call try { Uri trueUri = Uri.parse(o); return getContext().getContentResolver() .openFileDescriptor(trueUri, mode); } catch (RuntimeException ignore) {} throw new FileNotFoundException(); } // all other calls are forwarded the same way as above public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String o = uri.getQueryParameter(ORIGINAL_URI); if (TextUtils.isEmpty(o)) return null; try { Uri trueUri = Uri.parse(o); return getContext().getContentResolver().query(trueUri, projection, selection, selectionArgs, sortOrder); } catch (RuntimeException ignore) {} return null; } public String getType(Uri uri) { String o = uri.getQueryParameter(ORIGINAL_URI); if (TextUtils.isEmpty(o)) return "*/*"; try { Uri trueUri = Uri.parse(o); return getContext().getContentResolver().getType(trueUri); } catch (RuntimeException e) { return null; } } public Uri insert(Uri uri, ContentValues values) { return null; } public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } } 
+2
Jul 08 '15 at 4:52
source share

Well, the solution was already provided on SO, and you only had to look for it.

This is Paul Burke's answer. He wrote a utility class that returns the full file path for such a content path.

He says:

This will give the file path from MediaProvider, DownloadsProvider, and ExternalStorageProvider, returning to the unofficial ContentProvider that you mentioned.

and

They are taken from my open source library, aFileChooser .

FileUtils.java is where Paul Burke wrote the method you are looking for.

+1
Jul 08 '15 at 6:39
source share



All Articles