How to programmatically move , copy files to SD?

14.2k views Asked by At

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();
        
    }
}
3

There are 3 answers

4
Rahulrr2602 On BEST ANSWER

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.

9
CommonsWare On

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.

0
Anggrayudi H On

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
    }
})