1

To select an audio file using Intent , I am using this function :-

fun selectAudioFromStorage() {
    val pictureActionIntent = Intent(Intent.ACTION_GET_CONTENT, null)
    pictureActionIntent.type = "audio/*"
    pictureActionIntent.putExtra("return-data", true)
    startActivityForResult(pictureActionIntent, ResultConstants.RC_SELECT_AUDIO)
}

onActivityResult of the fragment class provides the Uri of the selected audio file like this :

content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Forganfinale.mp3

On Android (Lollipop),To convert the uri to real path is working fine using cursor but for Oreo devices I am not able to get the real path from the Uri of this audio.

Rakesh Verma
  • 766
  • 6
  • 14
  • The question is what are you trying to do with a file path? As of API 29 file paths are unusable outside of your App's private directory (See https://developer.android.com/training/data-storage#scoped-storage ) – Andrew Nov 25 '19 at 13:18
  • The file path is of audio file and after selection is done by user, file will be played in Audio player and clicking the submit button in the screen will upload it to server after user's selection – Rakesh Verma Nov 25 '19 at 14:10

3 Answers3

2

As file paths are not usable in API 29 and above for public files I suggest that you don't try to use them but use a Java FileDescriptor instead.

As per https://developer.android.com/training/data-storage#scoped-storage

And more specifically https://developer.android.com/training/data-storage/shared/media#open-file-descriptor

A lot of API's allow you to provide a FileDescriptor instead of a path.

e.g. Use https://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.io.FileDescriptor)

instead of

https://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.lang.String)

To get a fileDescriptor from a URI do the following (in Java)


ParcelFileDescriptor pfd =
                this.getContentResolver().
                        openFileDescriptor(URI, "r");

FileDescriptor fileDescriptor = pfd.getFileDescriptor();

Andrew
  • 8,198
  • 2
  • 15
  • 35
1

I usually use this Class for getting the path,, You can have a look at this which works for almost all version

public class RealPathUtil {

    public static String getRealPath(Context context, Uri fileUri) {
        String realPath;
        // SDK < API11
        if (Build.VERSION.SDK_INT < 11) {
            realPath = RealPathUtil.getRealPathFromURI_BelowAPI11(context, fileUri);
        }
        // SDK >= 11 && SDK < 19
        else if (Build.VERSION.SDK_INT < 19) {
            realPath = RealPathUtil.getRealPathFromURI_API11to18(context, fileUri);
        }
        // SDK > 19 (Android 4.4) and up
        else {
            realPath = RealPathUtil.getRealPathFromURI_API19(context, fileUri);
        }
        return realPath;
    }


    @SuppressLint("NewApi")
    public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
        String[] proj = {MediaStore.Images.Media.DATA};
        String result = null;

        CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
        Cursor cursor = cursorLoader.loadInBackground();

        if (cursor != null) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            result = cursor.getString(column_index);
            cursor.close();
        }
        return result;
    }

    public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
        String[] proj = {MediaStore.Images.Media.DATA};
        Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
        int column_index = 0;
        String result = "";
        if (cursor != null) {
            column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            result = cursor.getString(column_index);
            cursor.close();
            return result;
        }
        return result;
    }

    /**
     * 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
     */
    @SuppressLint("NewApi")
    public static String getRealPathFromURI_API19(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;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context       The context.
     * @param uri           The Uri to query.
     * @param selection     (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    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;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    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());
    }

}

Reference: https://gist.github.com/tatocaster/32aad15f6e0c50311626

Vinay Jayaram
  • 1,030
  • 9
  • 29
  • I tried your solution , but getting NumberFormatException in this else block :- 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); } – Rakesh Verma Nov 25 '19 at 11:10
  • try adding this line before converting to long : str = str.replaceAll("[^\\d.]", ""); that way it won't be any confusion in case your String contains letters or symbols. – Vinay Jayaram Nov 25 '19 at 11:37
  • the value passed to Long.valueOf() is the id variable and this id is like a string of alphabets and not the string of numbers. By the way I found the solution. Thanks bro – Rakesh Verma Nov 25 '19 at 12:27
1

I solved it. Simply call this getPathFromURI method and it will return the file path

object RealPathHelper {

private const val BUFFER_SIZE = 1024 * 2


@TargetApi(Build.VERSION_CODES.O)
fun getPathFromURI(
    context: Context,
    contentUri: Uri,
    fileNamePrefix: String,
    defaultFileExtension: String
): String? {
    val uriPath: String = contentUri.path ?: return null
    val fileName: String = MediaFileHelper.getFileNameWithExtension(uriPath)

    if (fileName.isNotBlank()) {
        val destFile =
            createOutputFile(context, contentUri, fileNamePrefix, defaultFileExtension)
        copyUriToFile(context, contentUri, destFile)
        return destFile.absolutePath
    }
    return null
}

private fun createOutputFile(
    context: Context,
    contentUri: Uri,
    fileNamePrefix: String,
    defaultFileExtension: String
): File {
    var count = 0
    var file: File

    val uriPath: String? = contentUri.path
    val fileExtension = if (uriPath == null) defaultFileExtension
    else MediaFileHelper.getFileExtension(uriPath)

    do {
        count++

        val mFileName = "$fileNamePrefix${StringHelper.getUniqueId()}$count$fileExtension"
        val newFilePath =
            "${context.getExternalFilesDir(null)?.absolutePath}${context.getString(R.string.audio_select_directory)}/$mFileName"

        file = File(newFilePath)

    } while (file.exists() && !file.isDirectory)

    return file
}

private fun copyUriToFile(context: Context, srcUri: Uri, dstFile: File) {
    try {
        val inputStream = context.contentResolver.openInputStream(srcUri) ?: return
        val outputStream = FileOutputStream(dstFile)
        inputStream.copyTo(outputStream, BUFFER_SIZE)
        inputStream.close()
        outputStream.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
Rakesh Verma
  • 766
  • 6
  • 14