Android: Getting a file URI from a content URI?
Asked Answered
K

11

192

In my app the user is to select an audio file which the app then handles. The problem is that in order for the app to do what I want it to do with the audio files, I need the URI to be in file format. When I use Android's native music player to browse for the audio file in the app, the URI is a content URI, which looks like this:

content://media/external/audio/media/710

However, using the popular file manager application Astro, I get the following:

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

The latter is much more accessible for me to work with, but of course I want the app to have functionality with the audio file the user chooses regardless of the program they use to browse their collection. So my question is, is there a way to convert the content:// style URI into a file:// URI? Otherwise, what would you recommend for me to solve this problem? Here is the code which calls up the chooser, for reference:

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

I do the following with the content URI:

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

Then do some FileInputStream stuff with said file.

Kane answered 14/4, 2011 at 0:54 Comment(2)
What calls are you using that don't like content URIs?Mclemore
There are a lot of content Uris where you cannot get the file path, because not all content Uris have filepaths. Don't use filepaths.Bourke
S
193

Just use getContentResolver().openInputStream(uri) to get an InputStream from a URI.

http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)

Separation answered 14/4, 2011 at 1:21 Comment(14)
Would this conflict with a user who uses Astro to get a file Uri?Kane
Check the scheme of the URI returned to you from the chooser activity. If if uri.getScheme.equals("content"), open it with a content resolver. If the uri.Scheme.equals("file"), open it using normal file methods. Either way, you'll end up with an InputStream that you can process using common code.Separation
Great thanks a ton! One more thing... I put the File path of the audio file the user chooses in a SharedPrefs field for the app so the user can preview the selected audio file at any time. When the scheme is file, this works fine. However with content this has some issues. Is there a way to successfully create a file with a content Uri?Kane
Actually, I just re-read the docs for getContentResolver().openInputStream(), and it works automatically for schemes of "content" or "file", so you don't need to check the scheme... if you can safely assume that it's always going to be content:// or file:// then openInputStream() will always work.Separation
Why do you need to create a file, exactly? You can open audio files via content URIs as well, using the Android MediaPlayer object.Separation
Ah, good point. I suppose I could do a simple uri.toString(); call on the content Uri, saved that in the SharedPrefs, then do a Uri.parse(uriString); to load the player... that should work right?Kane
Is there a way to get the File instead of the InputStream (from content:...)?Damato
Works for most things, except Google Drive. Any idea how to deal with Drive? (FileNotFoundException)Taka
@kilaka You can get the file path but it's painful. See https://mcmap.net/q/17801/-android-gallery-on-android-4-4-kitkat-returns-different-uri-for-intent-action_get_contentTaka
This answer is insufficient for someone who is using a closed-source API that relies on Files rather than FileStreams, but yet wants to use the OS to allow the user to select the file. The answer @DanyalAytekin referenced was exactly what I needed (and in fact, I was able to trim a lot of the fat because I know exactly what kinds of files I'm working with).Pyrethrin
@JasonLeBrun I am able to read from it using getContentResolver().openInputStream(uri) BUT I need a way to write into that..kindly helpHighwrought
this gives me a FileNotFoundExceptionQuail
Updated link for androidx: developer.android.com/reference/androidx/exifinterface/media/…Jeanejeanelle
@DanyalAytekin how can i get file from google drive and upload it to my server, do u have any idea now??Coray
J
52

This is an old answer with deprecated and hacky way of overcoming some specific content resolver pain points. Take it with some huge grains of salt and use the proper openInputStream API if at all possible.

You can use the Content Resolver to get a file:// path from the content:// URI:

String filePath = null;
Uri _uri = data.getData();
Log.d("","URI = "+ _uri);                                       
if (_uri != null && "content".equals(_uri.getScheme())) {
    Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
    cursor.moveToFirst();   
    filePath = cursor.getString(0);
    cursor.close();
} else {
    filePath = _uri.getPath();
}
Log.d("","Chosen path = "+ filePath);
Jewess answered 26/9, 2012 at 13:56 Comment(10)
Thanks, this worked perfectly. I couldn't use an InputStream like the accepted answer suggests.Isopropanol
How can i get other column values like MediaStore.Audio.Media.TITLE from Uri of type "file://" ?Blader
Works great on samsung galaxy nexus with 4.3Unifoliolate
This works only for local files, eg it does not work for Google DriveHoplite
how about getting same thing for audioNeurotomy
Sometimes works, sometimes returns file:///storage/emulated/0/... which doesn't exists.Tinfoil
Is the column "_data" (android.provider.MediaStore.Images.ImageColumns.DATA) always guaranteed to exist if the scheme is content://?Stotinka
This is a major anti-pattern. Some ContentProviders do provide that column, but you are not guaranteed to have read/write access to File when you try to bypass ContentResolver. Use ContentResolver methods to operate on content:// uris, this is the official approach, encouraged by Google engineers.Audryaudrye
this method needs an update, android.provider.MediaStore.Images.ImageColumns.DATA is deprecated as of nowPentachlorophenol
ImageColumns.DATA is deprecated now. What to do.Redouble
T
28

Try this....

get File from a content uri

fun fileFromContentUri(context: Context, contentUri: Uri): File {
    // Preparing Temp file name
    val fileExtension = getFileExtension(context, contentUri)
    val fileName = "temp_file" + if (fileExtension != null) ".$fileExtension" else ""

    // Creating Temp file
    val tempFile = File(context.cacheDir, fileName)
    tempFile.createNewFile()

    try {
        val oStream = FileOutputStream(tempFile)
        val inputStream = context.contentResolver.openInputStream(contentUri)

        inputStream?.let {
            copy(inputStream, oStream)
        }

        oStream.flush()
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return tempFile
}

private fun getFileExtension(context: Context, uri: Uri): String? {
    val fileType: String? = context.contentResolver.getType(uri)
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
}

@Throws(IOException::class)
private fun copy(source: InputStream, target: OutputStream) {
    val buf = ByteArray(8192)
    var length: Int
    while (source.read(buf).also { length = it } > 0) {
        target.write(buf, 0, length)
    }
}
Talcahuano answered 22/10, 2020 at 18:16 Comment(1)
Your answer reallyyyyyyyyyyyy help me, thanks man, thaaaanks <3Authenticity
P
14

If you have a content Uri with content://com.externalstorage... you can use this method to get absolute path of a folder or file on Android 19 or above.

public static String getPath(final Context context, final Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

You can check each part of Uri using println. Returned values for my SD card and device main memory are listed below. You can access and delete if file is on memory, but I wasn't able to delete file from SD card using this method, only read or opened image using this absolute path. If you find a solution to delete using this method, please share.

SD CARD

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

MAIN MEMORY

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

If you wish to get Uri with file:/// after getting path use

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Pippas answered 23/10, 2017 at 12:49 Comment(3)
i don't think that's the right way to do it in an application. sadly i'm using it in a quicky projectRockbound
@Rockbound Yes, you should work with Content Uris instead file paths or File Uris. That's way storage access framework is introduced. But, if you wish to get file uri this is the most correct way of other answers since you use DocumentsContract class. If you check out Google samples in Github, you will see that they also use to this class to get sub folders of a folder.Pippas
And DocumentFile class also new addition in api 19 and how you use uris from SAF. The correct way is to use a standard path for you app and ask user to give permission for a folder through SAF ui, save Uri string to shared preferences, and when it's needed access to folder with DocumentFile objectsPippas
M
14

Inspired answers are Jason LaBrun & Darth Raven. Trying already answered approaches led me to below solution which may mostly cover cursor null cases & conversion from content:// to file://

To convert file, read&write the file from gained uri

public static Uri getFilePathFromUri(Uri uri) throws IOException {
    String fileName = getFileName(uri);
    File file = new File(myContext.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
        FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
        outputStream.flush();
    }
    return Uri.fromFile(file);
}

To get filename use, it will cover cursor null case

public static String getFileName(Uri uri) {
    String fileName = getFileNameFromCursor(uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

There is good option to converting from mime type to file extention

 public static String getFileExtension(Uri uri) {
    String fileType = myContext.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

Cursor to obtain name of file

public static String getFileNameFromCursor(Uri uri) {
    Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}
Mccluskey answered 5/3, 2020 at 5:50 Comment(1)
Thanks, been looking at this for a week. Don't like having to copy a file for this but it does work.Hampshire
A
9

Trying to handle the URI with content:// scheme by calling ContentResolver.query()is not a good solution. On HTC Desire running 4.2.2 you could get NULL as a query result.

Why not to use ContentResolver instead? https://mcmap.net/q/17802/-getrealpathfromuri-not-working-with-ics-amp-picasa-based-images

Anticoagulant answered 19/3, 2015 at 10:59 Comment(2)
But sometimes we only need the path. We don't really have to load the file into memory.Schuh
"The path" is useless if you don't have access rights for it. For example, if an application gives you a content:// uri, corresponding to file in it's private internal directory, you will not be able to use that uri with File APIs in new Android versions. ContentResolver is designed to overcome this kind of security limitations. If you got the uri from ContentResolver, you can expect it to just work.Audryaudrye
Y
4

Well I am bit late to answer,but my code is tested

check scheme from uri:

 byte[] videoBytes;

if (uri.getScheme().equals("content")){
        InputStream iStream =   context.getContentResolver().openInputStream(uri);
            videoBytes = getBytes(iStream);
        }else{
            File file = new File(uri.getPath());
            FileInputStream fileInputStream = new FileInputStream(file);     
            videoBytes = getBytes(fileInputStream);
        }

In the above answer I converted the video uri to bytes array , but that's not related to question, I just copied my full code to show the usage of FileInputStream and InputStream as both are working same in my code.

I used the variable context which is getActivity() in my Fragment and in Activity it simply be ActivityName.this

context=getActivity(); //in Fragment

context=ActivityName.this;// in activity

Yasmin answered 5/2, 2018 at 11:28 Comment(1)
I know it is not related to the question, but how would you then use the byte[] videoBytes;? Most answers only show how to use InputStream with an image.Wafture
I
1

You can use the following android package which can be easier a bit for you

https://github.com/Blankj/AndroidUtilCode

Using the above package the code can be like

To Import use below Line

import com.blankj.utilcode.util.UriUtils;

Your code can be like

File f = UriUtils.uri2File(result);

Thanks

Idolah answered 28/4, 2022 at 19:5 Comment(0)
J
0

If you're absolutely sure that your content: Uri points to a real file, you can do the following trick:

context.contentResolver.openFileDescriptor(uri, "r").use { fileDescriptor ->
    if (fileDescriptor == null) {
        throw NullPointerException("ParcelFileDescriptor from $uri was null")
    }
    val path = "/proc/${android.os.Process.myPid()}/fd/${fileDescriptor.fd}"
    val fileUri = Uri.fromFile(File(path).canonicalFile)
}

Here we open the file using ContentResolver, construct a path to an underlying Linux file descriptor and resolve a real file path by getting canonicalFile.

Jit answered 22/5, 2023 at 7:54 Comment(1)
Does not work with samsung, it seems that they have restricted these calls java.lang.SecurityException: MediaProvider: User 11518 does not have read permission on file:///proc/8285/fd/25Panteutonism
I
0
private String getNativePath(String javaString) {
    Uri uri = Uri.parse(javaString);
    ContentResolver resolver = this.getApplicationContext().getContentResolver();
    ParcelFileDescriptor pfd = null;
    try {
        pfd = resolver.openFileDescriptor(uri, "r");
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
    return "pipe:" + pfd.detachFd();
};

you can get the fd and return a fd string to native code

Icebound answered 29/1, 2024 at 7:51 Comment(0)
T
-2

you can get filename by uri with simple way

Retrieving file information

fun get_filename_by_uri(uri : Uri) : String{
    contentResolver.query(uri, null, null, null, null).use { cursor ->
        cursor?.let {
            val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            it.moveToFirst()
            return it.getString(nameIndex)
        }
    }
    return ""
}

and easy to read it by using

contentResolver.openInputStream(uri)
Tetramethyldiarsine answered 6/12, 2020 at 18:43 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.