27

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

CocoNess
  • 4,213
  • 4
  • 26
  • 43
CyBer2t
  • 524
  • 1
  • 5
  • 12
  • I am getting URI from the intent Uri mediaUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); – CyBer2t Oct 30 '12 at 06:19

10 Answers10

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();
      }
    }
}
Adam Varhegyi
  • 11,307
  • 33
  • 124
  • 222
Shiva
  • 559
  • 3
  • 3
  • 4
    on the Android 5.x (Lollipop), must include: - Create empty file for ‘destinationFilename’ before using FileOutputStream to avoid throw IOException! – hungtdo May 19 '15 at 08:28
  • 1
    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. – Eugen Pechanec Sep 25 '19 at 06:54
  • Use ContentResolver: Change sourceuri.getPath() with this: https://stackoverflow.com/a/13275282/8283938 – Falke Design Oct 06 '19 at 10:36
12

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.

Ankur
  • 1,268
  • 18
  • 22
11
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;
}
AurumTechie
  • 166
  • 3
  • 14
PKV
  • 147
  • 1
  • 3
  • Hi, why is a `buffer[]` needed? Is that specific to saving a `Uri`? – Azurespot Mar 30 '15 at 02:07
  • @Azurespot 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://stackoverflow.com/a/5445161/4687402 – Benjamin Basmaci Mar 01 '19 at 16:06
8

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);
                }
            }
        }
    }
Sebi
  • 133
  • 1
  • 9
  • 1
    ... 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). – ZF007 Dec 18 '19 at 15:56
  • 4
    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. – Sebi Dec 19 '19 at 08:02
4

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();
    }
}
Ε Г И І И О
  • 11,199
  • 1
  • 48
  • 63
3

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();
    }
}
Pierre
  • 8,397
  • 4
  • 64
  • 80
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.

AppMobiGurmeet
  • 719
  • 3
  • 6
  • 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. – CyBer2t Oct 30 '12 at 06:26
  • @CyBer2t Then you need a file copy.Here is the stackoverflow [internal link](http://stackoverflow.com/questions/4615693/android-file-copy) showing file copy. – AppMobiGurmeet Oct 30 '12 at 06:30
  • 1
    uri.toString and uri.toPath() don't always work, especially with the gallery. I suspect using inputstreams is the way to go. – ssimm Feb 03 '16 at 15:09
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.

philoopher97
  • 772
  • 1
  • 6
  • 18
  • For anyone who has problems with this topic: I had to use ```openOutputStream(destination)``` instead of ```openFileDescriptor(uri, "w")``` – Matei Macoveiciuc May 04 '22 at 18:23
1

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)
            }
        }
    }
}
David Aleksanyan
  • 2,953
  • 4
  • 29
  • 39
-2

You can do it using

new File(uri.getPath());