1

I am making an android app which lets the user transfer some files from internal storage to external storage(SD card) using SAF.

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

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, 42);

And this is my onActivityResult

 public  void onActivityResult(int requestCode, int resultCode, Intent data) {      
    treeUri = data.getData();
    final int takeFlags = data.getFlags()
        & (Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
 }

I am taking permission for whole SD card so that i can move or delete any file in sd card any time .The problem is that even after taking permission i am not able to move or delete any file in SD card.

How should i move a file from internal Storage to external(SD card) for android 5.0 and above.

EDIT:This is the code i am using to move files.

public static void MoveFiles(File src, File dst) throws IOException
{
    FileChannel inChannel = new FileInputStream(src).getChannel();
    FileChannel outChannel = new FileOutputStream(dst).getChannel();
    try
    {
        inChannel.transferTo(0, inChannel.size(), outChannel);
    }
    finally
    {
        if (inChannel != null)
            inChannel.close();
        if (outChannel != null)
            outChannel.close();
           src.delete();
        
    }
}
Gary Chen
  • 248
  • 2
  • 14
Rahulrr2602
  • 701
  • 1
  • 13
  • 34

3 Answers3

4

This is how I finally solved the issue.

First call an intent to let the user grant write permission for whole SD card.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        startActivityForResult(intent, 42);

Then on onActivityResult take write permission.

public  void onActivityResult(int requestCode, int resultCode, Intent data) {
    treeUri = data.getData();
    final int takeFlags = data.getFlags()
        & (Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
}

And this is the code I used to copy files to SD card.

public static boolean copy(File copy, String directory, Context con) {
    static FileInputStream inStream = null;
    static OutputStream outStream = null;
    DocumentFile dir= getDocumentFileIfAllowedToWrite(new File(directory), con);
    String mime = mime(copy.toURI().toString());
    DocumentFile copy1= dir.createFile(mime, copy.getName());
    try {
        inStream = new FileInputStream(copy);
        outStream =
                con.getContentResolver().openOutputStream(copy1.getUri());
        byte[] buffer = new byte[16384];
        int bytesRead;
        while ((bytesRead = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, bytesRead);

        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {

            inStream.close();

            outStream.close();


            return true;


        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return false;
}

The above code uses the following two methods.

getDocumentFileIfAllowedToWrite method which returns DocumentFile if allowed to write.

public static DocumentFile getDocumentFileIfAllowedToWrite(File file, Context con) {
    List<UriPermission> permissionUris = con.getContentResolver().getPersistedUriPermissions();

    for (UriPermission permissionUri : permissionUris) {
        Uri treeUri = permissionUri.getUri();
        DocumentFile rootDocFile = DocumentFile.fromTreeUri(con, treeUri);
        String rootDocFilePath = FileUtil.getFullPathFromTreeUri(treeUri, con);

        if (file.getAbsolutePath().startsWith(rootDocFilePath)) {

            ArrayList<String> pathInRootDocParts = new ArrayList<String>();
            while (!rootDocFilePath.equals(file.getAbsolutePath())) {
                pathInRootDocParts.add(file.getName());
                file = file.getParentFile();
            }

            DocumentFile docFile = null;

            if (pathInRootDocParts.size() == 0) {
                docFile = DocumentFile.fromTreeUri(con, rootDocFile.getUri());
            } else {
                for (int i = pathInRootDocParts.size() - 1; i >= 0; i--) {
                    if (docFile == null) {
                        docFile = rootDocFile.findFile(pathInRootDocParts.get(i));
                    } else {
                        docFile = docFile.findFile(pathInRootDocParts.get(i));
                    }
                }
            }
            if (docFile != null && docFile.canWrite()) {
                return docFile;
            } else {
                return null;
            }

        }
    }
    return null;
}

And mime method which returns the mime type of the file.

   public static String mime(String URI) {
       static String type;
       String extention = MimeTypeMap.getFileExtensionFromUrl(URI);
       if (extention != null) {
           type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extention);
       }
       return type;
   }

I have tested the above code with Android Lollipop and Marshmallow.

EDIT: This is the link to the FileUtils class.

Gary Chen
  • 248
  • 2
  • 14
Rahulrr2602
  • 701
  • 1
  • 13
  • 34
2

I am taking permission for whole SD card so that i can move or delete any file in sd card any time

No, you are not. You are taking permission for whatever document tree the user chooses, to be able to work with that document tree, using the Storage Access Framework APIs (e.g., DocumentFile).

The problem is that even after taking permission i am not able to move or delete any file in SD card

That is because you are trying to work with the filesystem APIs. You have no rights to work with removable storage via those APIs, outside of the specific areas designated for access by your app (e.g., getExternalFilesDirs()).

If you want to work with the document tree:

  • Use DocumentFile.fromTreeUri() to create a DocumentFile pointing at the root of that tree

  • Use methods on that DocumentFile to see what is that level of the tree, get DocumentFile objects on branches of that tree, get DocumentFile objects on content in that tree, etc.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I am new to Android development. I am having some list of files that have to be moved to SD card. How should i get DocumentFile from a File and how to move a Document file to any Folder in SD card . – Rahulrr2602 Dec 19 '16 at 16:43
  • @Rahulrr2602: Use `DocumentFile` methods, like `createFile()`, to get a `DocumentFile` for where in the document tree you want to add some content. Call `getUri()` on that `DocumentFile`, then pass that to `openOutputStream()` on a `ContentResolver`. From there, use standard Java I/O (e.g., open a `FileInputStream` on the source file, then copy the bytes from the `InputStream` to the `OutputStream`). – CommonsWare Dec 19 '16 at 16:48
  • How should i create a DocumentFile in any directory or folder in SD card. I only have the path to that directory. – Rahulrr2602 Dec 19 '16 at 17:04
  • @Rahulrr2602: "I only have the path to that directory" -- AFAIK, you do not even have that. You have no idea what the directory structure is of removable storage. The typical approach is to use `ACTION_OPEN_DOCUMENT_TREE` to allow *the user* to pick the location where you should place content. If you wish to create your own subtrees under that user-chosen location, use `createDirectory()` on `DocumentFile`. – CommonsWare Dec 19 '16 at 17:08
  • First i am making the user give permission for the whole SD card using SAF. The i am making the user select the path where the file has to be transferred using a custom dialog box. The directory can also be a sub-directory . So i am having uri of SD card , path of file to be transferred and path of directory where the file has to transferred. – Rahulrr2602 Dec 19 '16 at 17:25
  • @Rahulrr2602: "First i am making the user give permission for the whole SD card using SAF" -- you have no way to force the user to do this. "The i am making the user select the path where the file has to be transferred using a custom dialog box" -- it would be far simpler to have the user simply choose this location via `ACTION_OPEN_DOCUMENT_TREE`. – CommonsWare Dec 19 '16 at 17:37
  • Is there any way to get uri from a File so that i can create a new DocumentFile in the selected directory and then transfer . – Rahulrr2602 Dec 19 '16 at 17:49
  • @Rahulrr2602: You can call `Uri.fromFile()` to get a `Uri` pointing at a `File`. I doubt that this will be useful to you. – CommonsWare Dec 19 '16 at 17:51
  • After searching a lot i found this post https://stackoverflow.com/questions/36242311/android-get-documentfile-with-write-access-for-any-file-path-on-sd-card-havin?rq=1 which gives DocumentFile from File. Will it be fine if i use this and get DocumentFile of the directory then create a new file in it and then transfer. – Rahulrr2602 Dec 19 '16 at 17:53
0

I would offer a simpler way. You can use an extension function DocumentFile.moveFileTo():

val sourceFile: DocumentFile = ...
val targetFolder: DocumentFile = DocumentFileCompat.fromSimplePath(context, storageId = "AAAA-BBBB", basePath = "Music")
// replace AAAA-BBBB with your SD card's ID

sourceFile.moveFileTo(context, targetFolder, callback = callback = object : FileCallback {
    override fun onConflict(destinationFile: DocumentFile, action: FileCallback.FileConflictAction) {
        handleFileConflict(action)
    }

    override fun onFailed(errorCode: FileCallback.ErrorCode) {
        // do stuff
    }

    override fun onCompleted(file: Any) {
        // do stuff
    }
})
Anggrayudi H
  • 14,977
  • 11
  • 54
  • 87