How to Consume Content From a Uri

Since it seems even clearer that the file scheme is going away, let’s review how this is all supposed to work now.

Today, let’s look at the consumer side: you are handed a Uri to some content, in a situation where you might have previously expected a file path. How do you get that content?

What To Do

First, ensure that anywhere you have an <intent-filter> for a file scheme, that you also have an <intent-filter> for content, scoped for the appropriate MIME type(s). You may get a content Uri by other means (e.g., onActivityResult()), but if you are supporting file-like content for actions like ACTION_VIEW, be sure to support both files and content Uri values.

Next, understand what the role is of the Uri you are being handed. Some Uri values point to database records, such as if you let the user pick a contact. That’s fine, but that’s not what I am covering here. Other Uri values point to stuff that might be some sort of file — that’s what this blog post is for.

Then, if the Uri has a content scheme, you can do the following with a ContentResolver instance:

  • Call openInputStream() to get an InputStream to read in the content

  • Call getType() to get the MIME type of the data backed by that stream

  • Call query(), asking for the OpenableColumns, where you can get the size of the content and some form of human-recognizable “display name” associated with the content

The first two of these are reminiscent of how you consume an http or https Uri, and so if you already have code for that, you may be able to refactor to limit code duplication.

And that’s pretty much it.

What Not To Do

Unfortunately, not everyone likes that solution, even though it’s really the only supported option.

Just Grab the Path

Some developers call getPath() on the Uri, then try to open that as a file (e.g., new File(uri.getPath())).

DO NOT DO THIS.

This was never reliable, as it assumes a file scheme and further assumes that you have direct filesystem access to the location pointed to by the path.

If the Uri has any other scheme, such as content, the path is largely meaningless to you.

Treat the Uri as an opaque handle. Trying to pick pieces out of that Uri and work with the pieces is unlikely to work out well for you.

Pretend That the MediaStore Knows What You’re Talking About

Some developers try to query the MediaStore for its DATA column, in hopes that they can convert a Uri to a filesystem path.

DO NOT DO THIS.

Not every content Uri comes from the MediaStore. In fact, nowadays, relatively few do. MediaStore knows nothing about Uri values from other providers. Plus even if you get a MediaStore Uri, that might be indexing media on removable storage, and you have no direct filesystem access to that.

Try to Derive a Path

Some developers try a more sophisticated variant on the above approaches, with two tons of code to look at the authority of the content Uri and try to derive a filesystem path based upon heuristics worked out for different authorities.

DO NOT DO THIS.

The fact that this is even possible is partly Google’s fault, for not making their Uri values more obscure.

However, it too is not reliable:

  • Apps are welcome to change their Uri structures, and so the heuristics that work today may not work tomorrow.

  • You may not have access to the file even if you can come up with the path.

  • There is about to be a Cambrian explosion of apps publishing their own content using their own providers and their own content Uri values, courtesy of file being banned. You need to support these providers, so you need to use the openInputStream() approach anyway.

Fictionally-Asked Questions

Here’s a FAQ list, just with a different definition of the “F”.

How Do I Get the Filename?

You don’t.

You’re welcome to look at the last path segment of the Uri. It might be a filename. It might not. There is no requirement that a content Uri use a valid filename as the last path segment.

The DISPLAY_NAME that you can get from the aforementioned OpenableColumns might be a filename, but it might not. The term “display name” does not necessarily imply a filename.

Besides, depending on where this content is coming from, there might not be an actual file that the user created that ties to this content. Suppose somebody enters a long-form note on a note-taking app, and that app makes that available via a Uri for other apps to access. The user did not create a file, upload a file, or have anything else file-esque to do with this note. Even if there is a genuine filename for it, that filename was app-generated and will not have meaning for the user.

OK, Then How Do I Get the File Extension?

You don’t.

Once again, you’re welcome to see if the Uri ends in what looks like a filename. There is no guarantee that something of the form foo.bar is actually a file with a file extension of .bar, of course.

You can use MimeTypeMap, or similar sorts of converters, to try to take the MIME type that you get from getType() and derive a file extension. However, those converters only handle popular MIME types and cannot handle arbitrary ones.

What If I Need to Pass a File to a Library?

First, double-check the library to see whether it has an InputStream variant of the File-laden method that you are trying to use. If it does, use the InputStream one.

Next, consider replacing the library with one that does have an InputStream option.

Finally, make a local copy of the content, by getting an InputStream from the ContentResolver, getting a FileOutputStream on some local file (e.g., inside getCacheDir() for internal storage), and using Java file I/O to copy from the InputStream to the OutputStream. flush(), getFD().sync(), and close() the FileOutputStream, and you now have a file that you can pass to the library.

Of course, this is a copy of the content, and so it may become stale. This approach is fine for a one-time operation, and fine for cases where you legitimately need a local copy (more on this below).

How Long Can I Use the Uri?

Not long.

Assuming that nothing happens on the provider’s side, the Uri should be valid for the duration of your process, but it may not be valid after that.

Think of a Uri as being akin to a deep-link URL to some content that requires authentication. The URL that you get may be good right now, because the user has an authenticated session. The URL may be good for a little while longer, so long as the session is still around. But eventually that session will time out, and the URL will not be useful any longer.

But, What If I Need the Content for Longer Than That?

You can call takePersistableUriPermission() on a ContentResolver. If the provider offered persistable permissions, and you take them, the system will remember that you have access to the Uri. Done carefully, you can use this to have access to the content indefinitely, or until the user moves, deletes, or otherwise renders the content inaccessible via the old Uri.

The problem is that takePersistableUriPermission() does not tell you if it worked. You have to call getPersistedUriPermissions() and see if you got it. There may have been no persistable permissions to take.

Your other option is to make a local copy, as noted earlier in the FAQ. Now, you are in control over your copy of the content. This may require some adjustment to your UI and terminology. Instead of using verbs like “link” (suggesting that the content is resident elsewhere), use verbs like “import”, “attach”, or “copy”, to emphasize the fact that the content is being copied. You might offer some sort of “refresh”, “replace”, or “update” option, where the user can get you a fresh Uri that you can use to replace the existing local copy with a fresh local copy.

Why Is This Such a Pain?

Google’s vision is for content Uri values to be the Android equivalent of http/https in Web apps: universally understood and used. After all, a ContentProvider can make this content available from:

  • files, including ones that other parties cannot access
  • BLOB columns in a database
  • encrypted material that gets decrypted on the fly
  • material that is stored over a network and needs to be downloaded and cached
  • material that is generated on the fly (think Web service-style JSON publishing)

Using content Uri values is much more flexible and offers more fine-grained security options.