Virtual Files FAQ (Sorta)

I largely ignored virtual files with the N Developer Previews, because what little documentation exists is self-contradictory. I couldn’t figure out why anyone would care about them, and the lack of documentation suggests that Google does not care much about them, so why worry?

But, an Ian Lake Stack Overflow answer scared me, which is why I have poked around a bit at virtual files to come up with this set of fictionally-asked questions. Hopefully, these will help frame the role of virtual files plus highlight some of the places where we have “known unknowns” regarding them.

What is a Virtual File?

A virtual file is an extension on an existing under-discussed concept in Android: a Uri does not have to map to anything directly usable by arbitrary apps.

Say What, Now?

A Uri is a largely-opaque identifier, representing some piece of content (or, occasionally, a collection of content). Roughly speaking, you can divide the uses of a Uri into two groups:

  1. The app holding the Uri needs the actual content itself, such as needing the bytes of an image to decode into a Bitmap and show in an ImageView

  2. The app holding the Uri is merely an intermediary, getting the Uri from one place (e.g., MediaStore) and passing it to another place (e.g., an ACTION_VIEW-supporting activity that happens to handle the content type identified by that Uri)

A Uri representing a virtual file can only be used in the second scenario. You cannot get at the content of a virtual file.

Why Does the Documentation Say That We Should Get a Stream, Then?

Our primary documentation on Android 7.0’s virtual files — all five paragraphs of it — has:

The virtual files feature allows your DocumentsProvider to return document URIs that can be used with an ACTION_VIEW intent even if they don’t have a direct bytecode representation.

It also has:

Since an app cannot directly open a virtual file by using the openInputStream() method, your app does not receive any virtual files if you include the CATEGORY_OPENABLE category.

However, it then has:

Your app can retrieve the URI of the virtual file and get an input stream

Other evidence, such as puttering around the Android source code, suggests that the first two statements are correct. Besides, there are two of those, and only one contradicting it, and so majority rules, right?

Perhaps the documentation will be clarified someday.

What Can You Do With a Virtual File?

Use it with an ACTION_VIEW Intent. No other use cases are documented, as far as I can tell.

What’s This About CATEGORY_OPENABLE?

The Storage Access Framework documentation has long suggested that you add CATEGORY_OPENABLE to your ACTION_OPEN_DOCUMENT and related Intents. The documentation for CATEGORY_OPENABLE indicates that the role of this category is to limit responses to those where you get a Uri that supports openInputStream() and related methods on ContentResolver.

What is implied by the virtual files “documentation” is that if you only need the Uri, and not the content, you could skip CATEGORY_OPENABLE, and the user might have more choices of where to pick content from.

I am Using ACTION_GET_CONTENT, So This Doesn’t Concern Me, Right?

Wrong. That’s what scared me about Ian Lake’s Stack Overflow answer that I mentioned earlier. According to him, ACTION_GET_CONTENT without CATEGORY_OPENABLE might return a Uri that represents a virtual file. This is perfectly reasonable from the documentation, but I have not seen very many examples of ACTION_GET_CONTENT with CATEGORY_OPENABLE, and I have not seen where anyone ran into a case where ACTION_GET_CONTENT returned a Uri that could not be opened.

Are There Other Intent Actions For Which CATEGORY_OPENABLE Applies?

ACTION_GET_CONTENT, ACTION_OPEN_DOCUMENT, and ACTION_CREATE_DOCUMENT are the three Intent actions for which CATEGORY_OPENABLE is documented to apply.

Beyond that, I have no idea.

In principle, any Intent action that is documented to return a Uri, such as ACTION_PICK, should be subject to the same rules. However, some developers may not have CATEGORY_OPENABLE on their <intent-filter>, even though they return openable Uri values.

Also, note that there are Uri values other than those for virtual files that cannot be opened. For example, the Uri for a contact is not openable. Hence, the standard Contacts app does not have CATEGORY_OPENABLE on its ACTION_PICK activity’s <intent-filter>.

Is Anyone Serving Virtual Files?

Until this gets better documentation, hopefully nobody is.

In reality, Google rarely adds something to Android without a vision in mind, so I would not be the least bit surprised if an app like Google Drive started offering up virtual files.

Why Would I Want to Receive a Virtual File?

If your app supports the notion of linking your own content to other content accessible to the user, expanding your scope to include virtual files may give the user more possible things to link.

However, since you do not have access to the actual content, you cannot upload that content to a server. Hence, this is only good for an on-device link feature, not an “attach”, “import”, or other verbs that implies that your app has access to the content itself. In particular, it is not useful for cases where the user might be working with your app’s data somewhere other than the original device (e.g., a Web app).

OK, So What Really Changed in Android 7.0?

A DocumentsProvider — the source of the content that the user browses in the Storage Access Framework UI — can include FLAG_VIRTUAL_DOCUMENT for the metadata on a document, to indicate that it is a virtual document and should be filtered out for clients requesting openable Uri values.

So, What Should I Do?

If you are using ACTION_GET_CONTENT, and you are trying to use the content itself (e.g., you pass the Uri to Picasso to populate an ImageView), add CATEGORY_OPENABLE to your Intent.

Experts might start contemplating where requesting virtual files, or possibly serving them from ACTION_GET_CONTENT or a DocumentsProvider, might be useful for their apps.

Beyond that, sit tight, and wait for more documentation.

(note: it might be a long wait, so be sure to pack a lunch)