Get a DocumentFile that is a child of a document tree without using findFiles()

1.7k views Asked by At

I have a tree URI that I got from ACTION_OPEN_DOCUMENT_TREE, how can I get a DocumentFile that is a child of this tree without using findFiles()? Given that I know how to get its documentId, its absolute path or its URI. I need the permissions associated with the document tree.

This is what I have right now:

DocumentFile rootTree = DocumentFile.fromTreeUri(context, rootTreeUri);
DocumentFile docFile = rootTree.findFile(fileName);

It returns docFile as a TreeDocumentFile, child of my root. I have write permission and can use createFile() on it since it is under the document tree from ACTION_OPEN_DOCUMENT_TREE.

So it works but findFile() is really slow.

If I try to use DocumentsContract like so:

// I know how to build the documentId from the file's absolute path
Uri uri = DocumentsContract.buildTreeDocumentUri(rootTreeUri.getAuthority(), documentId);
DocumentFile docFile = DocumentFile.fromTreeUri(context, uri);
// Result: "content://com.android.externalstorage.documents/tree/1A0E-0E2E:myChildDirectory/document/1A0E-0E2E:myChildDirectory"

It returns a new TreeDocumentFile rooted at docFile, not docFile as a child of my original document tree (root). So I don't have write permission on this tree.

And if I try like so:

Uri docUri = DocumentsContract.buildDocumentUriUsingTree(rootTreeUri, documentId);
// Result: "content://com.android.externalstorage.documents/tree/1A0E-0E2E:/document/1A0E-0E2E:myChildDirectory"

I get a URI that actually looks like what I want, but it's a URI, not a DocumentFile.

If I do the same as above but build a DocumentFile from this uri with fromTreeUri():

Uri docUri = DocumentsContract.buildDocumentUriUsingTree(rootTreeUri, documentId);
DocumentFile docFile = DocumentFile.fromTreeUri(context, docUri);
// Result: "content://com.android.externalstorage.documents/tree/1A0E-0E2E:/document/1A0E-0E2E:"

I get the original tree DocumentFile, not a DocumentFile representing the child.

2

There are 2 answers

3
ianhanniballake On BEST ANSWER

What you want is not possible with the current APIs. The only way to get make a TreeDocumentFile (the private subclass of DocumentFile that supports createFile, createDirectory, listFiles, and renameTo) is through DocumentFile.fromTreeUri (which only gives you the root tree URI as you've found) or through an existing TreeDocumentFile's listFiles() method, which is what findFile uses internally.

You should file a feature request on issuetracker.google.com to add a new static method that does what you need.

0
Maxim Mikhisor On

There is possible solution:

https://www.reddit.com/r/androiddev/comments/orytnx/fixing_treedocumentfilefindfile_lousy_performance/

Below is citation from above link:

  @Nullable
static public DocumentFile findFile(@NonNull context, @NonNull DocumentFile documentFile, @NonNull String displayName) {


    if(!(documentFile instanceof TreeDocumentFile)) {
        return documentFile.findFile(displayName);
    }

    final ContentResolver resolver = context.getContentResolver();
    final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(documentFile.getUri(),
            DocumentsContract.getDocumentId(documentFile.getUri()));

    Cursor c = null;
    try {
        c = resolver.query(childrenUri, new String[] {
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME,
        }, null, null, null);

        if(c != null) {
            while (c.moveToNext()) {
                if (displayName.equals(c.getString(1))) {
                    return new TreeDocumentFile(documentFile,
                            context,
                            DocumentsContract.buildDocumentUriUsingTree(documentFile.getUri(), c.getString(0)));
                }
            }
        }
    } catch (Exception e) {
        Log.w(TAG, "query failed: " + e);
    } finally {                 
        IOUtils.closeQuietly(c);
    }

    return null;
}

Note that for accessing package protected class TreeDocumentFile, you will have to put function above in a helper class in package androidx.documentfile.provider. After this, replace all calls of DocumentFile#findFile by this replacement or a Kotlin adaptation of it.