9

I want to save a file on a SD card folder.

And I can't use V4 support on my project.

So I call:

Intent itent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
itent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
itent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(itent, requestCodeTree);

Then on the onActivityResult, I have:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);

    if (resultCode == RESULT_OK) {
        switch(requestCode) {
            case requestCodeTree:
                saveFile(intent.getData());
                break;
        }
    }
}

And the code for saveFile is:

   private void saveFile(Uri data) {
        ContentResolver contentResolver = context.getContentResolver();

        InputStream in = null;
        OutputStream out = null;

        try {

            // Problems start here ************************
            Uri toUriFile= getUriBackupFile(context, data);
            // ********************************************

            if (toUriFile==null) {
                Uri toUriFolder = DocumentsContract.buildDocumentUriUsingTree(data, DocumentsContract.getTreeDocumentId(data));
                toUriFile = DocumentsContract.createDocument(contentResolver, toUriFolder, "", backupName);
            }

            out = contentResolver.openOutputStream(toUriFile);
            in = new FileInputStream(fromFile);

            byte[] buffer = new byte[1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();
            // write the output file (the file is now copied)
            out.flush();
            out.close();

        } catch (FileNotFoundException e) {
            Log.e(TAG, "Failed", e);
        } catch (Exception e) {
            Log.e(TAG, "Failed", e);
        }
    }

So far so good.

Problems start when I call getUriBackupFile to get the uri of the destination file.

To do that, I query the ContentResolver with buildChildDocumentsUriUsingTree and try to filter the result where DocumentsContract.Document.COLUMN_DISPLAY_NAME matches my file's display name, like this:

private static Uri getUriBackupFile(Context context, Uri treeUri) {
        final ContentResolver resolver = context.getContentResolver();

        final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
                treeUri,
                DocumentsContract.getTreeDocumentId(treeUri));

        Cursor c = null;
        try {
            String[] projections = new String[] {
                    DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                    DocumentsContract.Document.COLUMN_DISPLAY_NAME};

            // this line doesn't seem to have any effect !
        String selection = DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = '" + backupName + "' ";
            // *************************************************************************

            c = resolver.query(childrenUri, projections, selection, null, null);

            if (c!=null && c.moveToFirst()) {
            // Here I expect to have c.getCount() == 1 or == 0
            // But actually c.getCount() == [Number of children in the treeUri] regardless of the selection
                String documentId = c.getString(0);
                Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,
                        documentId);
                return documentUri;
            }

        } catch (Exception e) {
            Log.w(TAG, "Failed query: " + e);
        } finally {
            if (c!=null) c.close();
        }

        return null;
    }

But the query always return all the children of the treeUri, regardless of the selection. So, it seems the selection has no effect.

I could always loop through all the results, but if the selected folder has a large number of files it won't be good for the performance.

So my questions are:

  1. How I can filter the result of my query?
  2. Is this even the right approach to save a file on a sd card path?
Simon
  • 1,890
  • 19
  • 26

2 Answers2

1

The file system provider doesn't really support filtering:

https://github.com/aosp-mirror/platform_frameworks_base/blob/003ab94333bd6d47b35d7c241136d54b86a445b9/core/java/com/android/internal/content/FileSystemProvider.java#L370

The only choice is to get all rows and filter yourself.

David Liu
  • 9,426
  • 5
  • 40
  • 63
  • +1 This is unfortunately correct. I keep discovering how badly documented and implemented the `DocumentsContract` is. And the new Storage Access Framework makes it mandatory.... – Bip901 Dec 19 '21 at 20:15
0
  1. You need to add permission to read/write external storage in your manifest. Add this line before your application tag.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  1. If your app is running on device with android 6 or higher. you need to request permission before you can write to sd card/external storage. Follow this documentation on checking permissions at run time.

  2. Get external directory by using Environment.getExternalStorageDirectory(). This will return external directory.

Also refer to this documentation (https://developer.android.com/training/data-storage/files) and this question.

Mayank Kumar Chaudhari
  • 16,027
  • 10
  • 55
  • 122