Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get URI file path from inconsistent Android native file pick returned URIs

From Android N and upwards I am facing this issue. When I select a file from any apps on the file picker drawer on the left such as Gallery and File Manager, the URI's are received, and I can do what I need with the file(s).

Also if I select a file while the Images/Videos/Audio tab is highlighted in the file picker's drawer, any file I pick has it's URI returned successfully. enter image description here In my onActivity Result, Log.i("File URI to String", data.getDataString()); outputs I/File URI to String: content://com.android.providers.media.documents/document/image%3A20399which I am able to obtain the file path from using a GetPath Method below.

But when I just select from the picker with Internal or External storage highlighted, noting happens, Log.i("File URI to String", data.getDataString()); outputs I/File URI to String: content://com.android.externalstorage.documents/document/CA8B-8BD2%3ADCIM%2F100ANDRO%2FDSC_0001.JPG Which my getPath method is unable to get a path for and I think it might have something to do with the providers, looking at the differences between the uris in string form.

enter image description here

How I can get the file path of these selections not seemingly supported by any provider. Any help is appreciated.

My code to launch the file picker:

   Intent intent = new Intent();
   intent.setType("*/*");
   intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
   intent.setAction(Intent.ACTION_GET_CONTENT);
   startActivityForResult(intent, DEFAULT_IMAGE_PICK);

When I launch the file picker intent

2018-10-23 21:51:26.345 1572-10747/? I/ActivityManager: START u0 {act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=*/* cmp=com.android.documentsui/.picker.PickActivity (has extras)} from uid 10298
2018-10-23 21:51:26.352 1572-10747/? D/ActivityTrigger: activityStartTrigger: Activity is Triggerred in full screen ApplicationInfo{e1aa80b com.android.documentsui}
2018-10-23 21:51:26.352 1572-10747/? E/ActivityTrigger: activityStartTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.353 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.353 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.353 1572-10747/? D/ActivityTrigger: ActivityTrigger activityPauseTrigger 
2018-10-23 21:51:26.376 1572-10747/? D/ActivityTrigger: activityResumeTrigger: The activity in ApplicationInfo{e1aa80b com.android.documentsui} is now in focus and seems to be in full-screen mode
2018-10-23 21:51:26.376 1572-10747/? E/ActivityTrigger: activityResumeTrigger: not whiteListedcom.android.documentsui/com.android.documentsui.picker.PickActivity/26
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: mCompatibilityFlags - 0
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationDensity - 320
2018-10-23 21:51:26.380 1572-10747/? D/CompatibilityInfo: applicationScale - 1.0
2018-10-23 21:51:26.409 1572-2271/? I/InputDispatcher: Focus entered window: Window{9ac7c5c u0 com.android.documentsui/com.android.documentsui.picker.PickActivity}
2018-10-23 21:51:26.459 1572-1634/? I/ActivityManager: Displayed com.android.documentsui/.picker.PickActivity: +80ms

This is my Manifest file.I have implemented a provider share File Uris with other apps:

<provider
     android:name="android.support.v4.content.FileProvider"
     android:authorities="${applicationId}.provider"
     android:exported="false"
     android:grantUriPermissions="true">
         <meta-data
             android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/provider_paths" />
 </provider>

provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

In my onActivityResult

if ((requestCode == SELECT_GALLERY_FILE || requestCode == DEFAULT_IMAGE_PICK)
                && resultCode == Activity.RESULT_OK)
        {
           Log.i("File URI to String", data.getDataString());

            if(data.getData()!=null){//Receiving one file
                filelist.add(getPath(getActivity(),data.getData()));
                filelist = ListDeduplicator(filelist);
                Log.i("File Added", getPath(getActivity(),data.getData()));
                //Add Path of selected file to list of files to be loaded
            }

            if (data.getClipData() != null) {//Receiving several
                    ClipData mClipData = data.getClipData();
                    for (int i = 0; i < mClipData.getItemCount(); i++) {
                        //Add Paths of selected files to list of files to be loaded
                        ClipData.Item item = mClipData.getItemAt(i);
                        filelist.add(getPath(getActivity(),item.getUri()));
                        Log.i("File Added", getPath(getActivity(),item.getUri()));
                    }
                filelist = ListDeduplicator(filelist);

                }
            CheckAndExecute();
        }

My GetPath method implemented in onActivityResult

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

            //Log.i("CHECK", "1");


            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            //Log.i("CHECK", split[1]);

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }else{
                return Search_Dir(new File("/storage") ,split[1]);
                /*for (String string : getStorageDirectories(context)) {
                    String TestPath = string+"/"+split[1];
                    if ((new File(TestPath)).exists()) {
                        return TestPath;
                    }
                }*/

            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
            //Log.i("CHECK", "2");

            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)) {
            //Log.i("CHECK", "3");
            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())) {
        //Log.i("CHECK", "4");
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        //Log.i("CHECK", "5");
        return uri.getPath();
    }

    return null;

}
like image 871
Asiimwe Avatar asked Oct 23 '18 19:10

Asiimwe


3 Answers

You cannot get a common file URI from android's Intent.ACTION_GET_CONTENT. The URI may vary from different implementation of file providers. If you want to have a common URI, you have implement your own file picker or you can use some library.

If your requirement is to only send the file to the server then you can directly use the input stream from the URI and send this input stream to server.

See the SO question for more info.

The other way through which I have handled the scenario in my case is to get the path from GetPath method which you have mentioned and if it fails to give the path from URI then save the file somewhere in storage using the input stream provided by the URI. Below is the code to save the file:-

private void saveFileInStorage(final Uri uri) {

    new Thread(new Runnable() {
        @Override
        public void run() {
            File file = null;
            try {
                String mimeType = getActivity().getContentResolver().getType(uri);
                if (mimeType != null) {
                    InputStream inputStream = getActivity().getContentResolver().openInputStream(uri);

                        String fileName = getFileName(uri);
                        if (!fileName.equals("")) {
                            file = new File(
                                    getContext().getExternalFilesDir(
                                            Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/" + fileName);
                            OutputStream output = new FileOutputStream(file);
                            try {
                                byte[] buffer = new byte[inputStream.available()]; // or other buffer size
                                int read;

                                while ((read = inputStream.read(buffer)) != -1) {
                                    output.write(buffer, 0, read);
                                }

                                output.flush();
                                String path=file.getAbsolutePath();//use this path

                            } finally {
                                output.close();
                            }
                        }
                } 
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();

}

public String getFileName(Uri uri) {
    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    String displayName = "";
    Cursor cursor = null;
    if (getActivity() != null)
        cursor = getActivity().getContentResolver()
                .query(uri, null, null, null, null, null);
    try {
        // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return displayName;
}
like image 140
Nainal Avatar answered Nov 18 '22 19:11

Nainal


If I understand correctly, you are trying to read data provided by other applications, so the file provider configuration is not relevant.

I think the problem in your code is that you are trying to extract a file path from the received URI. This does not work reliably. There is no guaranty that the document ID contains a path or that you can query a _DATA column containing a path. Basically a content URI is supposed to be an opaque handle that can only used via ContentResolver's methods like :

  • openInputStream() to get the content

  • query() to get informations, like the size or a display name (the only guarantied columns)

Mark Murphy has a more detailed explanation

like image 45
bwt Avatar answered Nov 18 '22 19:11

bwt


Would you try this code in your Application.onCreate() and let me know if it works for you?

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
like image 35
Marzi Heidari Avatar answered Nov 18 '22 17:11

Marzi Heidari