Consuming Content? Be Flexible!

Many times, when we work with a content Uri, we get an InputStream, read in the content, and we’re good to go.

Sometimes, though, consumers of content make some unfortunate assumptions.

One assumption is that you can pass rw in for the mode on openFileDescriptor() and kin. First, this assumes that you have write access, which may or may not be true. Plus, rw requires that the content that you are working with be stored as a plain file on the filesystem somewhere.

Another related assumption is that the InputStream is seekable, supporting mark() and reset(). Once again, this only works if the content in question is backed by a plain file on the filesystem.

However, increasingly, that will not be the case, as more and more developers turn to publishing content, to get around things like the file Uri ban in Android 7.0. While content may be backed by a plain file on the filesystem, it might also be backed by:

  • A file, but one that needs to be transformed on the fly by the provider (e.g., decrypted)

  • A piece of a file (e.g., an asset packaged in the app’s APK)

  • Something in memory (e.g., the contents of a BLOB column that was read in from a database)

  • Something being streamed from the Internet (though this is kinda risky IMHO, on the provider’s part)

The ContentProvider publishing this content will be using something other than a file-based ParcelFileDescriptor, usually in the form of a pipe or socket pair. Those do not support rw mode, nor do they support seeking.

In particular, if your app is usable with read-only non-seekable content, but perhaps with a subset of functionality, make sure that you support it. For example, a couple of Android PDF viewers — including Google’s Drive PDF Viewer — seem to require rw access and crash in situations where other apps (e.g., Adobe Reader) work fine. Gracefully degrade in these cases: try for rw access, and then try for r access if the rw attempt fails.

If seekability is the issue, more so than rw access, you can try calling getStatSize() on the ParcelFileDescriptor that you get back from openFileDescriptor() on the ContentResolver. If this returns -1, then there is a good chance that the stream is not file-backed and will not support seeking. If this returns 0 or a positive value, that should be the size of the file that the stream is delivering to you.

If you need a test case for these cases, toss my StreamProvider in your test suite and test against an asset served via a content Uri. This will be a non-seekable stream on which rw is not an option (as you cannot modify an asset).

The majority of the time, when you get a content Uri, it will be served by a provider that is using a file for the content. In that case, rw mode may be available, and seeking should work. Just do not assume that you can always do that for every Uri. Make sure that you test cases where the Uri is something else, and that you handle those cases as best as you can.