Android: save a file from an existing URI
Asked Answered
G

10

28

How to save a media file (say .mp3) from an existing URI, which I am getting from an Implicit Intent?

Garrard answered 30/10, 2012 at 6:9 Comment(1)
I am getting URI from the intent Uri mediaUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);Garrard
D
36

Use this method, it works

void savefile(URI sourceuri)
{
    String sourceFilename= sourceuri.getPath();
    String destinationFilename = android.os.Environment.getExternalStorageDirectory().getPath()+File.separatorChar+"abc.mp3";

    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;

    try {
      bis = new BufferedInputStream(new FileInputStream(sourceFilename));
      bos = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
      byte[] buf = new byte[1024];
      bis.read(buf);
      do {
        bos.write(buf);
      } while(bis.read(buf) != -1);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (bis != null) bis.close();
        if (bos != null) bos.close();
      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}
Dam answered 30/10, 2012 at 6:46 Comment(3)
on the Android 5.x (Lollipop), must include: - Create empty file for ‘destinationFilename’ before using FileOutputStream to avoid throw IOException!Hegemony
This works just because sourceuri looks like file:///something and points to a file. This is no longer possible since API 24 (sharing file Uris between processes/apps is porhibited). Always use ContentResolver to open content Uris.Trickish
Use ContentResolver: Change sourceuri.getPath() with this: https://mcmap.net/q/189913/-contentresolver-how-to-get-file-name-from-uriAustralorp
Y
13

If Uri is received from Google Drive, it can be a Virtual File Uri too. Check this article from CommonsWare for more information. So you have to consider that condition too while saving file from Uri.

To find if file Uri is virtual or not you can use

private static boolean isVirtualFile(Context context, Uri uri) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if (!DocumentsContract.isDocumentUri(context, uri)) {
            return false;
        }
        Cursor cursor = context.getContentResolver().query(
                uri,
                new String[]{DocumentsContract.Document.COLUMN_FLAGS},
                null, null, null);
        int flags = 0;
        if (cursor.moveToFirst()) {
            flags = cursor.getInt(0);
        }
        cursor.close();
        return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
    } else {
        return false;
    }
}

You can get the stream data from this virtual file like this:

private static InputStream getInputStreamForVirtualFile(Context context, Uri uri, String mimeTypeFilter)
        throws IOException {

    ContentResolver resolver = context.getContentResolver();
    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);
    if (openableMimeTypes == null || openableMimeTypes.length < 1) {
        throw new FileNotFoundException();
    }
    return resolver
            .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
            .createInputStream();
}

For finding MIME type try

private static String getMimeType(String url) {
    String type = null;
    String extension = MimeTypeMap.getFileExtensionFromUrl(url);
    if (extension != null) {
        type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
    }
    return type;
}

Overall, you can use

public static boolean saveFile(Context context, String name, Uri sourceuri, String destinationDir, String destFileName) {

    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    InputStream input = null;
    boolean hasError = false;

    try {
        if (isVirtualFile(context, sourceuri)) {
            input = getInputStreamForVirtualFile(context, sourceuri, getMimeType(name));
        } else {
            input = context.getContentResolver().openInputStream(sourceuri);
        }

        boolean directorySetupResult;
        File destDir = new File(destinationDir);
        if (!destDir.exists()) {
            directorySetupResult = destDir.mkdirs();
        } else if (!destDir.isDirectory()) {
            directorySetupResult = replaceFileWithDir(destinationDir);
        } else {
            directorySetupResult = true;
        }

        if (!directorySetupResult) {
            hasError = true;
        } else {
            String destination = destinationDir + File.separator + destFileName;
            int originalsize = input.available();

            bis = new BufferedInputStream(input);
            bos = new BufferedOutputStream(new FileOutputStream(destination));
            byte[] buf = new byte[originalsize];
            bis.read(buf);
            do {
                bos.write(buf);
            } while (bis.read(buf) != -1);
        }
    } catch (Exception e) {
        e.printStackTrace();
        hasError = true;
    } finally {
        try {
            if (bos != null) {
                bos.flush();
                bos.close();
            }
        } catch (Exception ignored) {
        }
    }

    return !hasError;
}

private static boolean replaceFileWithDir(String path) {
    File file = new File(path);
    if (!file.exists()) {
        if (file.mkdirs()) {
            return true;
        }
    } else if (file.delete()) {
        File folder = new File(path);
        if (folder.mkdirs()) {
            return true;
        }
    }
    return false;
}

Call this method from an AsycTask. Let me know if this helps.

Yonit answered 6/12, 2017 at 6:22 Comment(0)
T
12
private static String FILE_NAM  = "video";
String outputfile = getFilesDir() + File.separator+FILE_NAM+"_tmp.mp4";

InputStream in = getContentResolver().openInputStream(videoFileUri);
private static File createFileFromInputStream(InputStream inputStream, String fileName) {

    try {
        File f = new File(fileName);
        f.setWritable(true, false);
        OutputStream outputStream = new FileOutputStream(f);
        byte buffer[] = new byte[1024];
        int length = 0;

        while((length=inputStream.read(buffer)) > 0) {
            outputStream.write(buffer,0,length);
        }

        outputStream.close();
        inputStream.close();

        return f;
    } catch (IOException e) {
        System.out.println("error in creating a file");
        e.printStackTrace();
    }

    return null;
}
Teter answered 24/10, 2013 at 4:38 Comment(2)
Hi, why is a buffer[] needed? Is that specific to saving a Uri?Ribble
@Ribble InputStream and OutputStream use a byte buffer to read and write. In inputStrea.read(buffer), the method takes the buffer array and fills it. The OutputStream, set to the appropriate filename, takes the buffer array and writes its contents. However, I found a very neat solution to take an input stream and convert it to a String instead. Put this in some sort of Util class and you are good to go. Take a look here: https://mcmap.net/q/36074/-how-do-i-read-convert-an-inputstream-into-a-string-in-javaGoethe
E
9

I have used following code to save a file from an existing Uri given back from an Intent to an Uri that my App hosts:

 private void copyFile(Uri pathFrom, Uri pathTo) throws IOException {
        try (InputStream in = getContentResolver().openInputStream(pathFrom)) {
            if(in == null) return;
            try (OutputStream out = getContentResolver().openOutputStream(pathTo)) {
                if(out == null) return;
                // Transfer bytes from in to out
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
            }
        }
    }
Embosom answered 18/12, 2019 at 15:26 Comment(2)
... as there are already posted 7 answer that look similar... please do explain in your answer why yours is beneficial over others (itherwise it belomes a low-quality answer and might be deleted at all). (From Review).Solorio
The other answers goes from an Uri to a file, in my answer it shows how to save a file from one Uri to another Uri.Embosom
G
5

Here's the easiest and the cleanest:

private void saveFile(Uri sourceUri, File destination)
    try {
        File source = new File(sourceUri.getPath());
        FileChannel src = new FileInputStream(source).getChannel();
        FileChannel dst = new FileOutputStream(destination).getChannel();
        dst.transferFrom(src, 0, src.size());
        src.close();
        dst.close();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}
Gravamen answered 18/7, 2014 at 14:40 Comment(7)
Hi, would destination be the entire File path including name of file? Or just the directory name/path?Ribble
Destination is type of File denoting the entire file with its name.Dine
Using this code I get java.io.FileNotFoundException: /document/image:18019: open failed: ENOENT (No such file or directory)Dowse
@FilipHazubski the exception speaks for itself.Dine
@ΕГИІИО It speaks that your code does not work with URI returned by the Intent opening gallery for choosing an image. At least it did not worked for me and probably someone who voted up my comment. I tested it with Android 5.1.1.Dowse
I don't think content uri's return an absolute path. Which is why this answer does not work with some Uris.Coheman
It's obviously wrong solution. Don't try to get path, content uri will not return direct file path. Use InputStream instead.Intrauterine
D
4

When receiving a android.net.Uri from an external source, the best way to save the file is from the stream:

try (InputStream ins = activity.getContentResolver().openInputStream(source_uri)) {
    File dest = new File(destination_path);
    createFileFromStream(ins, dest);
} catch (Exception ex) {
    Log.e("Save File", ex.getMessage());
    ex.printStackTrace();
}

createFileFromStream method:

public static void createFileFromStream(InputStream ins, File destination) {
    try (OutputStream os = new FileOutputStream(destination)) {
        byte[] buffer = new byte[4096];
        int length;
        while ((length = ins.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
        os.flush();
    } catch (Exception ex) {
        Log.e("Save File", ex.getMessage());
        ex.printStackTrace();
    }
}
Deaconess answered 7/8, 2019 at 9:35 Comment(0)
P
2

I slightly modified Sebi's answer to work for Kotlin:

fun copyUri(context: Context, pathFrom: Uri, pathTo: Uri?) {
    context.contentResolver.openInputStream(pathFrom).use { inputStream: InputStream? ->
        if (pathTo == null || inputStream == null) return
        context.contentResolver.openOutputStream(pathTo).use { out ->
            if (out == null) return
            // Transfer bytes from in to out
            val buf = ByteArray(1024)
            var len: Int
            while (inputStream.read(buf).also { len = it } > 0) {
                out.write(buf, 0, len)
            }
        }
    }
}
Priapism answered 12/7, 2022 at 13:33 Comment(0)
C
1

1.Create a file from a URI path as:

File from = new File(uri.toString());

2.Create another File where you want the file to save as:

File to = new File("target file path");

3.Rename the file as:

from.renameTo(to);

With this the file from default path is automatically deleted and created at the new path.

Closelipped answered 30/10, 2012 at 6:18 Comment(3)
Gurmeet, If i have to retain the existing file also, then how to proceed? I need to create a new file or a duplicate copy.Garrard
@Garrard Then you need a file copy.Here is the stackoverflow internal link showing file copy.Closelipped
uri.toString and uri.toPath() don't always work, especially with the gallery. I suspect using inputstreams is the way to go.Bigley
B
1

How to get external storage location and save a file

This answer is not for the question, but for the title. It took hours to figure out how to do this since no article explains the process totally, while some of them are years old and uses deprecated APIs. Hope this might be helpful for future developers.

Get location of External Storage

For instance, from inside a fragment,

// when user choose file location
private val uriResult = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
    // do when user choose file location
    createFile(uri)
}

fun openFileChooser() {
    // startActivityForResult() is deprecated
    val suggestedFileName = "New Document.txt"
    uriResult.launch(suggestedFileName)
}

Write file data using Uri

It may seem difficult to create a java.io.File from an android.net.Uri, since there is no direct way to convert an android.net.Uri into java.net.URI. But if you have the ApplicationContext you can do it very easily.

fun createFile(uri: Uri) {
    try {
requireContext().applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
        FileOutputStream(fd).use { fos ->

            // do your job on the FileOutputStream
            // also use background thread

            fos.close()
        }
    }
  } catch (e: Exception) {

  }
}

Note: File operations throws multiple exceptions, so handle them carefully. And also do file operations in worker threads.

Bighead answered 3/3, 2022 at 18:33 Comment(1)
For anyone who has problems with this topic: I had to use openOutputStream(destination) instead of openFileDescriptor(uri, "w")Tautologism
I
-2

You can do it using

new File(uri.getPath());
Irascible answered 11/12, 2019 at 12:14 Comment(1)
The uri.getPath() can resolve to something that is not a valid file path.Cockloft

© 2022 - 2025 — McMap. All rights reserved.