ACTION_OPEN_DOCUMENT: May Not Be Read-Write

While I think that the death of external storage in Android Q is a good move overall, and while I think that the Storage Access Framework is OK for basic scenarios, there will be “growing pains”.

One of those is the realization that not all document providers play fair.

For example, you might want to write to a user-supplied document. So, you use ACTION_OPEN_DOCUMENT to request that the user choose a document. You take the Uri that you get and eventually you call openOutputStream() on a ContentResolver, passing in that Uri. You then try to write content out to that stream, to be able to store that content in the user-chosen document.

That might not work.

For example, if the user chose the “Audio” category in the Storage Access Framework UI, you will crash on your openOutputStream() call, with an IllegalArgumentException and an error message of “Media is read-only”.

The tactical reason for the crash is that MediaDocumentsProvider – the implementation behind that “Audio” option – universally rejects write access:

@Override
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
        throws FileNotFoundException {
    final Uri target = getUriForDocumentId(docId);

    if (!"r".equals(mode)) {
        throw new IllegalArgumentException("Media is read-only");
    }

    // Delegate to real provider
    final long token = Binder.clearCallingIdentity();
    try {
        return getContext().getContentResolver().openFileDescriptor(target, mode);
    } finally {
        Binder.restoreCallingIdentity(token);
    }
}

And, in their document metadata, they correctly indicate that the document is not writable (i.e., they do not have FLAG_SUPPORTS_WRITE in the document’s flags).

The strategic problem, though, is that we have no means of telling ACTION_OPEN_DOCUMENT that we want a Uri that we can write to. As a result, ACTION_OPEN_DOCUMENT will happily let the user choose a piece of content that we cannot use. This gives the user a poor experience, as they will now get some cryptic error message from the app about not being able to write to that location.

This is a framework problem, so we are stuck with this implementation for the time being. With luck, and with your help, perhaps this gets addressed in the Android R timeframe, so that in several years, this problem will be behind us. Simply put: we need some way to stipulate whether we need read-write access to the content in ACTION_OPEN_DOCUMENT, with the Storage Access Framework UI limiting the user to read-write documents. The DocumentsContract.Document API already has FLAG_SUPPORTS_WRITE, so the Storage Access Framework UI has the information necessary to do the filtering.

In the short term, though, we are stuck with a Uri that may or may not work for writing.

Ideally, we would use DocumentFile to determine if we can write to a document. Unfortunately, it does not correctly examine the flags, so its canWrite() method returns true for a document in which we do not have write access. Instead, you need around 100 lines of code to check to see whether the flags for this document contain FLAG_SUPPORTS_WRITE, based on the the DocumentsContractApi19 implementation of canWrite().

One way or another, if you intend to write to a Uri that you get from ACTION_OPEN_DOCUMENT, confirm that you actually can write there first, before trying to use the Uri.