Android SAF, cannot copy file, FLAG_SUPPORTS_COPY not set

Viewed 748

I'm trying to copy a document with the SAF framework in Android by using DocumentsContract.copyDocument(ContentResolver, Uri, Uri) however this doesn't work, android returns error "Failed to copy document".

By narrowing the issue down, the FLAG_SUPPORTS_COPY is off on that document (according to DocumentsContract.Document#COLUMN_FLAGS) (COLUMN_FLAGS value is 326 in decimal). So this explains the error.

However moving the document is allowed (flag FLAG_SUPPORTS_MOVE is on) and file is really moved when calling DocumentsContract.moveDocument(ContentResolver, Uri, Uri, Uri)

Access to the document tree (both the root of the drive, and DCIM folder) have been granted through Intent.ACTION_OPEN_DOCUMENT_TREE

Why is the FLAG_SUPPORTS_COPY set to false for the Document ? Am I missing something ?

Note: I believe I fullfill the requirements from this post https://stackoverflow.com/a/58147682/15401262

Thank you

Code (java)

// docFilesToProcess if of type "DocumentFile[]" and contains "regular files, like images" (not directories).
// Create destination dir
Uri destUri = DocumentsContract.createDocument(this.getContentResolver(), docFilesToProcess[i].getParentFile().getUri(), DocumentsContract.Document.MIME_TYPE_DIR, "destDir");
Log.i("M", "destUri: "+ destUri.toString());
// Create document
Uri docToMove = DocumentsContract.createDocument(this.getContentResolver(), docFilesToProcess[i].getParentFile().getUri(), "text/plain", "text");
Log.i("M", "docToMove: "+ docToMove.toString());
// copy document
DocumentsContract.copyDocument(this.getContentResolver(), docToMove, destUri);

Output

I/M: destUri: content://com.android.externalstorage.documents/tree/primary%3ADCIM/document/primary%3ADCIM%2FdestDir
I/M: docToMove: content://com.android.externalstorage.documents/tree/primary%3ADCIM/document/primary%3ADCIM%2Ftext.txt
W/DocumentsContract: Failed to copy document
    java.lang.UnsupportedOperationException: Copy not supported
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.call(ContentProviderNative.java:658)
        at android.content.ContentResolver.call(ContentResolver.java:2042)
        at android.provider.DocumentsContract.copyDocument(DocumentsContract.java:1442)
        at com.example.exifthumbnailadder.MainActivity.addThumbs(MainActivity.java:1036)
        at java.lang.reflect.Method.invoke(Native Method)

Persistant permission request

    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addFlags(
        Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
            | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
1 Answers

In general, you cannot ever rely on any SAF provider to implement any optional feature. You should check whether the feature is supported, and you should have a viable fallback plan for when it is not.

ContentResolver cr = getContentResolver();
if((flags & FLAG_SUPPORTS_MOVE) == FLAG_SUPPORTS_MOVE)
    Uri newDoc = DocumentsContract.copyDocument(cr, docToCopy, destDir);
else {
    Uri newDoc = DocumentsContract.createDocument(cr, destDir, mimeType, name);
    manuallyCopyBytes(docToCopy, newDoc);
}

It's important to give the provider a chance to do it, because something like Google Drive may be able to perform the copy separately on the device and on the server, instead of copying it on the device and then having to upload the whole thing to the server again.

Related