Android: Open file with intent chooser from URI obtained by Storage Access Framework

michael

In the beginning the user can select files with the new Storage Access Framework (Assuming the app is API>19):

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

Then I save references to those chosen files by saving the URI`s which looks like:

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

(in this case the file is from the default downloads dir`).

Later, I want let the user to open those files (For example they names displayed in UI list, and the user selects one).

I want to do this with the Android famous intent chooser feature, and all I have is the above URI object...

Thanks,

user1643723

Edit: I have revised this answer to include the example code of approach I have initially referred to as "writing a specialized ContentProvider". This should fully satisfy requirements of the question. Probably makes the answer too big, but it has internal code dependencies now, so let's leave it as whole. The main point still holds true: use the ContentPrvder below if you want, but try to give file:// Uris to apps, that support them, unless you want to be blamed for someone's app crashing.

Original answer


I would stay away from Storage Access Framework as it is now. It's insufficiently backed by Google, and the support in apps is abysmal, making it is hard to tell between bugs in those apps and SAF itself. If you are confident enough (which really means "can use try-catch block better then average Android developer"), use Storage Access Framework yourself, but pass to others only good-old file:// paths.

You can use the following trick to get filesystem path from ParcelFileDescriptor (you can get it from 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's 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 must be prepared, that the method above will return null (the file is a pipe or socket, which is perfectly legitimate) or an empty path (no read access to file's parent directory). If this happens copy the entire stream to some directory you can access.

Complete solution


If you really want to stick with content provider Uris, here you go. Take the code of ContentProvider below. Paste into your app (and register it in AndroidManifest). Use getShareableUri method below to convert received Storage Access Framework Uri into your own. Pass that Uri to other apps instead of the original Uri.

The code below is insecure (you can easily make it secure, but explaining that would expand the length of this answer beyond imagination). If you care, use file:// Uris—Linux file systems are widely considered secure enough.

Extending the solution below to provide arbitrary file descriptors without corresponding Uri is left as exercise for reader.

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; }
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Android - open txt file from Intent Chooser

Open a Google Drive File Content URI after using KitKat Storage Access Framework

Get real path from URI, Android KitKat new storage access framework

Storage Access Framework - Saving Uri

File chooser to choose docx,pdf file from browsing local storage in phonegap cordova android application

Saving file using Storage Access Framework in Android

Open File With Intent (Android)

Android open file input stream from uri

WinUI3 cannot open storage file from Uri

Android File Chooser not calling from Android Webview

unable to pass bundle data from image File Chooser intent to onActivityResult

Get file path from Uri from Video Chooser

not getting how to use incoming image from android chooser intent

Android Xamarin: Open file from Storage (with path)

What is the use of Intent.FLAG_GRANT_PREFIX_URI_PERMISSION when requesting access to a folder using Storage access framework?

Get correct Uri from file chooser with google drive

Empty Chooser with ActionSend Intent in Android

Android intent chooser - navigation apps

Android. Custom Intent chooser

How to add Internal and External storage to File Chooser in android

File is read-only while picking from android storage access framework

Can't open file from intent in Android 10

How to open PDF file directly from URL in app chooser

Get FIle extension using Android Storage Access Framework

Android Storage Access Framework Document Create File & Dir Recursively

How to properly overwrite content of file using android storage access framework

Android: Incorrect File name for selected file(From file chooser)

Android file chooser

Storage Access Framework Getting Correct Uri Path Delete/Edit/Get File