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

com2ghz
  • 2,706
  • 3
  • 16
  • 27
  • What is your AOS version/API level? Could it be that such files you're having issues with are GoogleDrive files? – Stan Mar 21 '16 at 10:15
  • minSdkVersion 19 targetSdkVersion 23. The device I am testing runs on Android 5.0.2 – com2ghz Mar 21 '16 at 10:17
  • 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. – Ankit Mar 21 '16 at 11:18
  • @com2ghz hey, I know this is a year ago, but do you mind telling me if you built the file chooser your self? – hehe Feb 06 '17 at 11:07
  • 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. – com2ghz Feb 06 '17 at 15:04
  • If `file://` is banned, how do we get file contents? I'm about to start ripping my hair out... – Daniel Oct 24 '17 at 14:24
  • AttachmentUtils ? – J.K Nov 30 '17 at 09:47
  • 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)? – tuantv.dev Nov 03 '20 at 09:39

4 Answers4

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.

pvrforpranavvr
  • 2,708
  • 2
  • 24
  • 34
KJEjava48
  • 1,967
  • 7
  • 40
  • 69
  • 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. – com2ghz Mar 21 '16 at 11:20
  • 1
    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. – KJEjava48 Mar 21 '16 at 11:25
  • 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. – com2ghz Mar 21 '16 at 11:32
  • 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. – KJEjava48 Mar 21 '16 at 11:42
  • 1
    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. – com2ghz Mar 21 '16 at 12:42
  • 2
    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)`. –  Jun 23 '17 at 18:51
  • *this basic feature costs much efforts of Android developer*, your answer doesn't resolve all cases but pretty good, thank you. – Phong Nguyen Jan 11 '18 at 03:49
  • 2
    @user5395084 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()` – mirekkomis Feb 09 '18 at 14:18
  • Thanks. Hoping to see a little more description here – VVB Feb 20 '18 at 05:36
  • 8
    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. – Alexis Sep 18 '18 at 08:45
  • OK I found a working solution : https://stackoverflow.com/a/46485037/507323 – Alexis Sep 18 '18 at 12:39
  • Where does `filesize` come from? – hippietrail Aug 25 '19 at 07:03
  • 1
    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. – KJEjava48 Aug 25 '19 at 08:39
  • What is `FILE_SELECT_CODE`? I don't see it defined anywhere – OscarVanL Oct 17 '20 at 22:46
  • 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)? – tuantv.dev Nov 03 '20 at 09:35
  • Android Development is such a car crash sometimes... Thank you for this answer, it works very well. – OscarVanL Nov 16 '20 at 14:55
  • @OscarVanL 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. – Erhannis Sep 01 '21 at 19:47
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"
}
Vlad
  • 7,997
  • 3
  • 56
  • 43
  • 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. – sabith.ak Jul 15 '20 at 12:35
2

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
}
  • 1
    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 Device – Tom Truyen Jul 19 '21 at 15:48
  • I will use this answer as my solution, the best way is the simple-st wayy – Lyn Jun 08 '22 at 02:53
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

Atif Mukhtiar
  • 1,186
  • 9
  • 11