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.



