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
.