Android opening a file with ACTION_GET_CONTENT results into different Uri's
Asked Answered
T

4

42

I am trying to open files by using Intent.ACTION_GET_CONTENT. Android file browser

Dependent on the Android version/device brand the file browser opens and I get the following results:

Selecting a file from Downloads:

content://com.android.providers.downloads.documents/document/446

Selecting a file from Fotos:

content://media/external/images/media/309

Selecting a file from FileCommander:

file:///storage/emulated/0/DCIM/Camera/20141027_132114.jpg

I can open all these files except when I try to open a file from Downloads,, Audio , Afbeeldingen(Images)

It's likely I can't handle this kind of Uri: content://com.android.providers.downloads.documents/document/446

I have tried the following things:

  • Trying to open the file by new File(uri.getPath()). File.exists() returns false.
  • Trying to open/reach the file by getContext().getContentResolver().openInputStream(uri). Results into a FileNotFoundException
  • Trying to open the file with the following code:

    public static String getRealPathFromURI_API19(Context context, Uri uri) {
    
    Log.i("uri", uri.getPath());
    String filePath = "";
    if (uri.getScheme().equals("file")) {
        return uri.getPath();
    } else if (DocumentsContract.isDocumentUri(context, uri)) {
        String wholeID = DocumentsContract.getDocumentId(uri);
        Log.i("wholeID", wholeID);
        // Split at colon, use second item in the array
        String[] splits = wholeID.split(":");
    if (splits.length == 2) {
        String id = splits[1];
    
            String[] column = {MediaStore.Images.Media.DATA};
        // where id is equal to
            String sel = MediaStore.Images.Media._ID + "=?";
            Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    column, sel, new String[]{id}, null);
            int columnIndex = cursor.getColumnIndex(column[0]);
            if (cursor.moveToFirst()) {
                filePath = cursor.getString(columnIndex);
            }
            cursor.close();
        }
    } else {
        filePath = AttachmentUtils.getPath(context, uri);
    }
    return filePath;
    }
    

What am I doing wrong?

UPDATE: I have noticed that the files that are listed in the screenshot that they are not physically existing in the storage. The device I am using is a tablet from the company containing rubbish data. My colleague told me that this device once was connected with a different Google account. These files could be the files from the previous account which are not existing/reachable anymore.

My own conclusion about it is that I am encountering some bug in Android. My bug report

Update 6 february 2017:

Android banned the file:// URI. Please consider to think about it.

Ban on file: Uri Scheme The biggest compatibility issue so far with Android 7.0 is that the file: scheme for Uri values is banned, in effect. If you attempt to pass a file: Uri in an Intent that is going to another app — whether via an extra or as the “data” facet of the Intent — you will crash with a FileUriExposedException exception. You will face similar issues with putting file: Uri values on the clipboard in ClipData . This is coming from an updated edition of StrictMode . StrictMode.VmPolicy.Builder has a penaltyDeathOnFileUriExposure() method that triggers the detection of file: Uri values and the resulting FileUriExposedException exceptions. And, it appears that this is pre-configured, much as how StrictMode is pre-configured to apply penaltyDeathOnNetwork() (the source of your NetworkOnMainThreadException crashes).

Teodoor answered 21/3, 2016 at 10:8 Comment(8)
What is your AOS version/API level? Could it be that such files you're having issues with are GoogleDrive files?Boak
minSdkVersion 19 targetSdkVersion 23. The device I am testing runs on Android 5.0.2Teodoor
getContentResolver().openInputStream(uri) should work, unless there isn't anything at given location or you don't have proper permission to access the file at given Uri.Incapacious
@Teodoor hey, I know this is a year ago, but do you mind telling me if you built the file chooser your self?Thermoelectricity
No this is the native Android file picker. It could be different on some branded operating systems. So it' s even possible that you need a 3th party app to open the file browser like File Commander or TotalCommander.Teodoor
If file:// is banned, how do we get file contents? I'm about to start ripping my hair out...Toothache
AttachmentUtils ?Anthozoan
Could you get path from these URIs "content://com.android.providers.media.documents/document/document%3A80" (Documents) and "content://com.android.providers.downloads.documents/document/msf%3A81" (Downloads)?Jelly
S
62

Use below code.This will work for sure.

public static String getPath(Context context, Uri uri) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // DocumentProvider
        if (DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]};
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }
    return null;
}

public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {column};
    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.moveToFirst()) {
            final int index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

Use the below code to browse the file in any format.

public void browseClick() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("*/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    //intent.putExtra("browseCoa", itemToBrowse);
    //Intent chooser = Intent.createChooser(intent, "Select a File to Upload");
    try {
        //startActivityForResult(chooser, FILE_SELECT_CODE);
        startActivityForResult(Intent.createChooser(intent, "Select a File to Upload"),FILE_SELECT_CODE);
    } catch (Exception ex) {
        System.out.println("browseClick :"+ex);//android.content.ActivityNotFoundException ex
    }
}

Then get that file path in the onActivityResult like below.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == FILE_SELECT_CODE) {
        if (resultCode == RESULT_OK) {
            try {
              Uri uri = data.getData();

                if (filesize >= FILE_SIZE_LIMIT) {
                    Toast.makeText(this,"The selected file is too large. Selet a new file with size less than 2mb",Toast.LENGTH_LONG).show();
                } else {
                    String mimeType = getContentResolver().getType(uri);
                    if (mimeType == null) {
                        String path = getPath(this, uri);
                        if (path == null) {
                            filename = FilenameUtils.getName(uri.toString());
                        } else {
                            File file = new File(path);
                            filename = file.getName();
                        }
                    } else {
                        Uri returnUri = data.getData();
                        Cursor returnCursor = getContentResolver().query(returnUri, null, null, null, null);
                        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                        int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
                        returnCursor.moveToFirst();
                        filename = returnCursor.getString(nameIndex);
                        String size = Long.toString(returnCursor.getLong(sizeIndex));
                    }
   File fileSave = getExternalFilesDir(null);
    String sourcePath = getExternalFilesDir(null).toString();
    try {
                        copyFileStream(new File(sourcePath + "/" + filename), uri,this);

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
  }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
private void copyFileStream(File dest, Uri uri, Context context)
        throws IOException {
    InputStream is = null;
    OutputStream os = null;
    try {
        is = context.getContentResolver().openInputStream(uri);
        os = new FileOutputStream(dest);
        byte[] buffer = new byte[1024];
        int length;

        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);

        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        is.close();
        os.close();
    }
}

After this you can open this file from your application external storage where you saved the file with appropriate action.

Selenography answered 21/3, 2016 at 11:1 Comment(17)
I have tried this code and I get a valid file path now. I have noticed something new. The listed files from the screenshot do not exist inside the /storage/emulated/0/Download/ directory.Teodoor
if u got a valid file path then try to write that file to your application internal or external storage and then open the file from there.I will update my answer.Selenography
With valid I mean I get a valid directory format but the files are not existing 'physically'. I think I am facing some bug in Android. I am now trying to find out why these files are listed in the file browser but do not exist in the storage.Teodoor
ok.You can use the above code to browse the file and check whether it shows those physically non-existing files.if this post helps you to solve ur problem please don't forget to accept as answer or vote.Selenography
I'll just accept this answer. For some reason the file browser shows unexisting files. If I download a new file into the Downloads folder, then I can open the file correctly. I'm not sure if this is a bug in Android.Teodoor
Your code can't handle microSD paths, when a file is picked from microSD, the document ID contains XXXX-XXXX instead of "primary", someone will have to add an else to "primary".equalsIgnoreCase(type).Allover
this basic feature costs much efforts of Android developer, your answer doesn't resolve all cases but pretty good, thank you.Labiche
@Allover For me split[0] contained the XXXX-XXXX id of microSD so "/storage/"+split[0]+"/"+split[1] worked for SD card. You can check if it's correct by checking new File("/storage/"+split[0]+"/"+split[1]).exists()Findlay
Thanks. Hoping to see a little more description hereRepartition
In some cases (for me api 27 with a file I dropped in the emulator), the returned uri looks like "content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2FCprBPostLabel2850351.pdf" and getPath crashes when reconstructing the uri because the id is not convertible to a Long.Twiddle
OK I found a working solution : https://mcmap.net/q/17801/-android-gallery-on-android-4-4-kitkat-returns-different-uri-for-intent-action_get_contentTwiddle
Where does filesize come from?Hipparchus
i don't remember exactly , but u can find some ways to find the size.Looks like i skipped those parts in the answer bcos its not relevant here. if u want u can remove that if condition for ur convenience.Selenography
What is FILE_SELECT_CODE? I don't see it defined anywhereOgrady
Could you get path from these URIs "content://com.android.providers.media.documents/document/document%3A80" (Documents) and "content://com.android.providers.downloads.documents/document/msf%3A81" (Downloads)?Jelly
Android Development is such a car crash sometimes... Thank you for this answer, it works very well.Ogrady
@Ogrady I believe FILE_SELECT_CODE is whatever number you want it to be, as long as it doesn't conflict with any other request code you've defined. It's used to help you keep your activity requests straight.Rennes
A
3

Pick any file using System File Picker:

val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
startActivityForResult(intent, 1)

onActivityResult:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
        data?.data?.let {
            getFileFromUri(requireContext().contentResolver, uri, requireContext().cacheDir)
        }
    }
}

Get File:

private fun getFileFromUri(contentResolver: ContentResolver, uri: Uri, directory: File): File {
    val file =
        File.createTempFile("suffix", "prefix", directory)
    file.outputStream().use {
        contentResolver.openInputStream(uri)?.copyTo(it)
    }

    return file
}
Adamok answered 29/9, 2020 at 15:42 Comment(2)
This one deserves more credit, It might not be the best solution, but it is the easiest to implement and works perfectly for me for both external and internal storage of my Android DeviceMalpighi
I will use this answer as my solution, the best way is the simple-st wayyAuvergne
C
2

The accepted answer on kotlin

@Suppress("SpellCheckingInspection")
object PathCompat {

    @WorkerThread
    fun getFilePath(context: Context, uri: Uri): String? = context.run {
        return try {
            when {
                Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> getDataColumn(uri, null, null)
                else -> getPathKitkatPlus(uri)
            }
        } catch (e: Throwable) {
            Timber.e(e)
            null
        }
    }

    @Suppress("DEPRECATION")
    @SuppressLint("NewApi", "DefaultLocale")
    private fun Context.getPathKitkatPlus(uri: Uri): String? {
        when {
            DocumentsContract.isDocumentUri(applicationContext, uri) -> {
                val docId = DocumentsContract.getDocumentId(uri)
                when {
                    uri.isExternalStorageDocument -> {
                        val parts = docId.split(":")
                        if ("primary".equals(parts[0], true)) {
                            return "${Environment.getExternalStorageDirectory()}/${parts[1]}"
                        }
                    }
                    uri.isDownloadsDocument -> {
                        val contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"),
                            docId.toLong()
                        )
                        return getDataColumn(contentUri, null, null)
                    }
                    uri.isMediaDocument -> {
                        val parts = docId.split(":")
                        val contentUri = when (parts[0].toLowerCase()) {
                            "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                            "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                            "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                            else -> return null
                        }
                        return getDataColumn(contentUri, "_id=?", arrayOf(parts[1]))
                    }
                }
            }
            "content".equals(uri.scheme, true) -> {
                return if (uri.isGooglePhotosUri) {
                    uri.lastPathSegment
                } else {
                    getDataColumn(uri, null, null)
                }
            }
            "file".equals(uri.scheme, true) -> {
                return uri.path
            }
        }
        return null
    }

    private fun Context.getDataColumn(uri: Uri, selection: String?, args: Array<String>?): String? {
        contentResolver?.query(uri, arrayOf("_data"), selection, args, null)?.use {
            if (it.moveToFirst()) {
                return it.getString(it.getColumnIndexOrThrow("_data"))
            }
        }
        return null
    }

    private val Uri.isExternalStorageDocument: Boolean
        get() = authority == "com.android.externalstorage.documents"

    private val Uri.isDownloadsDocument: Boolean
        get() = authority == "com.android.providers.downloads.documents"

    private val Uri.isMediaDocument: Boolean
        get() = authority == "com.android.providers.media.documents"

    private val Uri.isGooglePhotosUri: Boolean
        get() = authority == "com.google.android.apps.photos.content"
}
Culdesac answered 30/3, 2020 at 9:36 Comment(1)
I've used this example to pick files. I can get files from internal storage, except "/documents". I can't pick files from external SD card too. The docId part is "home" for them.Thine
B
1

There is a bug I just faced

final String docId = DocumentsContract.getDocumentId(uri);

return different URI (e.g: content://com.android.providers.downloads.documents/document/11 and sometime content://com.android.providers.downloads.documents/document/abc%aile.jpg in that case Long.valueOf(id) throws an exception to fix that

String id = DocumentsContract.getDocumentId(uri);
                if (id.startsWith("raw:")) {
                    id = id.replaceFirst("raw:", "");
                    return id;
                }
                final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(contentUri, null, null);

do this return the id, it worked for me

Beatabeaten answered 10/12, 2019 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.