0

I'm trying to provide sd card access inside my app with Storage Access Framework.

This is how I call an intent to let the user choose the sd card directory.

private void openDocumentTree() {
    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);
    startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);
}

And this is how I manage the intent result to set permissions:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_CODE_OPEN_DOCUMENT_TREE:
                if (resultCode == Activity.RESULT_OK) {
                    Uri treeUri = data.getData();
                    int takeFlags = data.getFlags();
                    takeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION |
                                  Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        this.getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
                    }
               }
        }

But still can't save any file on sd card.

Value of treeUri on my device is:

content://com.android.externalstorage.documents/tree/6921-B9FD%3A

What I missed here that system still couldn't let the user have access to sd card(saving a simple file on sd card)?

Eftekhari
  • 1,001
  • 1
  • 19
  • 37
  • 2
    "This is how I call an intent to let the user choose sd card directory" -- you can get rid of that `addFlags()` call. None of those are relevant. Those are for the publishers of content, to tell the system what they are offering consumers of the content. You are the consumer. "But still can't save any file on sd card" -- you should be able to use a `ContentResolver` and `openOutputStream()`, after using `DocumentFile` and methods like `createDirectory()` or `createFile()`. Show your code for saving the content, and explain what errors you are getting (or what your symptoms are in general). – CommonsWare Jul 20 '16 at 21:01
  • This is how I'm trying to save on sd card. http://stackoverflow.com/questions/38445467/how-to-copy-image-to-an-existing-directory-on-sd-card – Eftekhari Jul 20 '16 at 21:09
  • Your comments on another post guided me to Storage Access Framework which helped me a lot. – Eftekhari Jul 20 '16 at 21:11
  • 2
    Bear in mind that the user does not have to choose removable storage for `ACTION_OPEN_DOCUMENT_TREE`. The user could choose Google Drive, Dropbox, or any other storage provider, none of which has anything to do with the hardcoded paths in your other question. And, if all you are doing is copying a single file, use `ACTION_CREATE_DOCUMENT` to get the `Uri`, then use `ContentResolver` and `openOutputStream()` to get the stream to write the content to, and save yourself some hassle. – CommonsWare Jul 20 '16 at 21:20
  • I'm trying to provide access to sd card for my gallery app. I want to user be able to move or copy images and videos from variety of paths to sd card directories as other gallery apps provided this capability nicely. That hardcoded path in my other question was just for making question more direct and more simple. So is it possible to check if user has chosen a correct directory on intent? because I checked some apps like `Quickpic` for choosing wrong directory and app warned me to try again and choose correct path. – Eftekhari Jul 20 '16 at 21:30
  • 1
    That doesn't change the fact that the *user* might choose some other storage provider. Beyond that, you should still be able to accomplish this using `DocumentFile` and `ContentResolver`, thereby handling those users who choose a different storage provider *and* handling removable media at the same time. "So is it possible to check if user has chosen a correct directory on intent?" -- not reliably. You can examine `getHost()` of your `Uri` and see if it is something that you expect, but I would expect that to break on different devices, as manufacturers mess with this stuff. – CommonsWare Jul 20 '16 at 21:34
  • Ok. I'm using this line of code `DocumentFile documentFilePickedDirectory = DocumentFile.fromTreeUri(this, treeUri);` and `createFile` to save myImage on sd card. `treeUri` is uri of my sd card and as the result myImage got saved on root directory of sd card of my device which is done as expected. Now I want to save myImage on a sub directory of my sd card root directory. How could I accomplish that for example for this path `"/storage/extSdCard/MyFolder/MyImage.jpg"`? How could I get Uri for this path as documentFile needs uri or file(which can't be created on sd card with non-SAF methods). – Eftekhari Jul 22 '16 at 06:53
  • 1
    `findFile()` to see if the subdirectory exists. `createDirectory()` to create it if it does not exist. – CommonsWare Jul 22 '16 at 10:54
  • How about finding a subdirectory of a directory with a similar displayName? like `"/storage/extSdCard/MyFolder/MyFolder"` as `findFile(String displayName)` Search through listFiles() for the ``first`` document matching the given display name. I tried getting path of each `DocumentFile` in `documentFilePickedDirectory` and compare it with `"/storage/extSdCard/MyFolder/MyFolder"` and no success out of it because the kind of path that `documentFile.getUri().getPath()` returns is like this `/tree/6921-B9FD:/document/6921-B9FD:LOST.DIR`. – Eftekhari Jul 22 '16 at 14:33
  • 1
    Don't call `getPath()`. *Never* call `getPath()` on a `Uri` unless the scheme is `file`. You can call `getDisplayName()` to get what the provider wants to use for a display name for a document identified by a `DocumentFile`. This display name does not have to be a directory name or a filename, as it depends on the storage provider. – CommonsWare Jul 22 '16 at 14:37
  • So you are saying that a directory with this actual path `"/storage/extSdCard/MyFolder"` doesn't have the same displayName with another directory with this actual path `"/storage/extSdCard/MyFolder/MyFolder"`? – Eftekhari Jul 22 '16 at 14:49
  • 1
    Again, that depends on the storage provider. I would expect the display names to both be "MyFolder", but that's just a guess. – CommonsWare Jul 22 '16 at 15:00
  • So the only solution remains is to rename displayName of all the similar displayName directories on sd card to differ in their displayName so in this way I could have access to a subDirectory with a similar name but with the different displayName. – Eftekhari Jul 22 '16 at 15:07
  • 1
    AFAIK, `listFiles()` on `DocumentFile` only lists immediate children, as does its `File` equivalent. Hence, you can distinguish `/storage/extSdCard/MyFolder` from `/storage/extSdCard/MyFolder/MyFolder` via depth in the document tree hierarchy. – CommonsWare Jul 22 '16 at 15:10
  • How is it that when I query `ContentResolver` right after creating a file with `documentFile.createFile(...)`, `ContentResolver` can't find newly added image but after a moment if run query again it is there? How could I manage this behaviour to run my query (AsyncTask) when `ContentResolver` is done with refreshing? (I think `documentFile.createFile` triggers refreshing of `ContentResolver`... am I right?) – Eftekhari Jul 24 '16 at 15:15
  • I suggest that you post a separate Stack Overflow question, where you provide your code for creating the file and using `ContentResolver`, and where you explain in a bit greater detail your symptoms (e.g., Java stack trace, if you are crashing). – CommonsWare Jul 24 '16 at 15:18
  • Yes. You are right. – Eftekhari Jul 24 '16 at 15:19
  • http://stackoverflow.com/q/38554843/2123400 – Eftekhari Jul 24 '16 at 17:37

1 Answers1

0

Ok. This is how I copy image on sd card with provided permissions which invokes file picker to choose sd card directory by the user (which may user choose a wrong directory so prepare your code for it).

private void newcopyFile(File fileInput, String outputParentPath,
                        String mimeType, String newFileName) {

    DocumentFile documentFileGoal = DocumentFile.fromTreeUri(this, treeUri);

    String[] parts = outputParentPath.split("\\/");
    for (int i = 3; i < parts.length; i++) { //ex: parts:{"", "storage", "extSdCard", "MyFolder", "MyFolder", "MyFolder"}
        if (documentFileGoal != null) {
            documentFileGoal = documentFileGoal.findFile(parts[i]);
        }
    }
    if (documentFileGoal == null) {
        Toast.makeText(MainActivity.this, "Directory not found", Toast.LENGTH_SHORT).show();
        return;
    }

    DocumentFile documentFileNewFile = documentFileGoal.createFile(mimeType, newFileName);

    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        outputStream = getContentResolver().openOutputStream(documentFileNewFile.getUri());
        inputStream = new FileInputStream(fileInput);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    try {
        if (outputStream != null) {
            byte[] buffer = new byte[1024];
            int read;
            if (inputStream != null) {
                while ((read = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, read);
                }
            }
            if (inputStream != null) {
                inputStream.close();
            }
            inputStream = null;
            outputStream.flush();
            outputStream.close();
            outputStream = null;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

NOTE: Read all the comments under the question.

And be aware of setting a ContentObserver to your ContentResolver If you want to get notified when your newly created image is registered inside ContentResolver and hence it's time to query it(if you wish to).

Community
  • 1
  • 1
Eftekhari
  • 1,001
  • 1
  • 19
  • 37
  • What is outputParentPath here? Please help me with my question here:- http://stackoverflow.com/q/39054454/6289068 It has active bounty also. Please help me with this. Really stuck here – Anku Agarwal Aug 22 '16 at 16:58
  • `yourFile.getParentFile()`, you must consider many situations that I'll tell you as an answer for your question. Don't worry it's an easy game. – Eftekhari Aug 23 '16 at 01:27
  • @Eftekhari I tried using your above method newcopyFile. But It shows No directory found. Please help me providing solution to my question at :- http://stackoverflow.com/q/39054454/6289068 – Ankesh kumar Jaisansaria Aug 23 '16 at 13:11