Proper way to get file path when it's picked using ACTION_GET_CONTENT
Asked Answered
T

1

6

I have a file picker implemented in my app like this:

Intent intent = new Intent();
intent.setType("*/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Title"), FILE_PICK);

It's a known issue that you can't easily get the actual file location this way because Intent will return some weird Uri that you can't really use to create a File object.

I'm using this method to get the actual File path:

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
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)) {
        // 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;
}

This works for some files, and for some not. Right now I noticed that it doesn't work when I pick a file from "Downloads" folder. It skips all if statements above and returns null.

What would be the correct way, that would be more reliable to get the actual selected File path?

Trantham answered 18/1, 2016 at 15:58 Comment(2)
"I have a file picker implemented in my app like this" -- that is not a "file picker". "What would be the correct way..." -- the "correct way" is to recognize that there does not have to be a file, because there does not have to be a file. ACTION_GET_CONTENT does not pick a file. It picks content. A Uri is an opaque handle to some content, just as the URL in a Web browser is an opaque handle to some content. You use methods on ContentResolver to access content identified by a Uri, just as you use methods on HttpUrlConnection to access content identified by a URL.Sententious
That makes sense. Thank you for your explanation very much. Could you maybe let me know what be the correct way to implement this? I need a way to be able to pick any file on Android device so that I can upload it to my backend. This doesn't seem to be working as intended since it recognizes some files and some not.Trantham
S
5

I have a file picker implemented in my app like this

That is not a "file picker". It is a content picker.

I need a way to be able to pick any file on Android device so that I can upload it to my backend.

Um, well, that depends a fair bit on how literal you are in that sentence.

ACTION_GET_CONTENT is not a problem in general. However, what you get are not necessarily files. However, you can still upload them, assuming that whatever you are using for the uploading allows you to upload from an InputStream. If so, use openInputStream() on a ContentResolver, passing in the Uri, to get the InputStream to use.

If you really need it to be actual files, you can implement a file-picker UI directly in your app. There are libraries for this. However, you will not be able to access all files — in particular, you cannot use this to upload files from removable storage on Android 4.4+.

Sententious answered 18/1, 2016 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.