Get real file path from URI on Android 10 , Also content URI is suffixed with msf:documentid in Android 10

Viewed 2237

I was trying to get the real path from intent chooser in Xamarin Android and its working fine in every android version except android 10 . In android 10 content uri comes like

content://com.android.providers.downloads.documents/document/msf%3A180

3 Answers

Android 10 has scoped storage enabled by default. This means you cannot directly get access to the external storage path - take a look here.

You can do what is suggested by setting requestLegacyExternalStorage to true in your app's AndroidManifest.xml, but keep in mind this will only work for targetSdk 29 and not for 30+.

If you're trying to access media, this should be done through the MediaStore. Otherwise, take a look at the scoped storage docs.

after getting the URI of the image file of course we need to edit this file by compressing it, after that getting a real file path.if you targeting SDK 30, this is my implementation using scoped storage ;) don't forget to upvoting my answer.

import android.content.ContentValues
import android.content.Context
import android.content.res.AssetFileDescriptor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream


/**
* Created by Mostafa Anter on 11/5/20.
*/

object PhotoHelper{

@RequiresApi(Build.VERSION_CODES.Q)
        /**
         * mContext: context from activity or fragment,
         * imageSelectedUri: uri that return from any pick picture library it usually return inside on activity response method
         * appFolderName: name of folder that will generate to save compressing images ;)
         * createdImageCompressedName : random name of new compressed image 
         */
fun compressGetImageFilePath(
        mContext: Context,
        imageSelectedUri: Uri,
        appFolderName: String,
        createdImageCompressedName: String = System.currentTimeMillis().toString()
): String {
    //region Getting the photo to process it
    val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        ImageDecoder.decodeBitmap(ImageDecoder.createSource(mContext.contentResolver, imageSelectedUri))
    } else {
        mContext.contentResolver.openInputStream(imageSelectedUri)?.use { inputStream ->
            BitmapFactory.decodeStream(inputStream)
        }
    }
    // endregion

    //region save photo to gallery using Scoped storage
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, createdImageCompressedName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/$appFolderName/")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val imageUri = mContext.contentResolver.insert(collection, values)
    //endregion


    //region save image
    mContext.contentResolver.openOutputStream(imageUri!!).use { out ->
        bitmap!!.compress(Bitmap.CompressFormat.PNG, 100, out)
    }

    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    mContext.contentResolver.update(imageUri, values, null, null)
    //endregion


    //region get file path of content uri
    val file = File(mContext.cacheDir, "$createdImageCompressedName.png")
    try {
        val assetFileDescriptor: AssetFileDescriptor = mContext.contentResolver.openAssetFileDescriptor(imageUri, "r")!!

        val inputStream = FileInputStream(assetFileDescriptor.fileDescriptor)
        val outputStream = FileOutputStream(file)
        inputStream.copyTo(outputStream)

        inputStream.close()
        outputStream.close()


    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    }
    //endregion



    return file.path

    }
}

so easy if you want to target old and new devices simply make if statement to check android version if greater or equal android Q use my method else use your old method.

You can query the fileName, and then build the path.

  • Query the file name:

      public static string getFileName(Context context, Android.Net.Uri uri)
      {
          ICursor cursor = null;
          string[] projection = {
              MediaStore.MediaColumns.DisplayName
          };
    
          try
          {
              cursor = context.ContentResolver.Query(uri, projection, null, null,
              null);
              if (cursor != null && cursor.MoveToFirst())
              {
                  int index = cursor.GetColumnIndexOrThrow(MediaStore.MediaColumns.DisplayName);
                  return cursor.GetString(index);
              }
          }
          finally
          {
              if (cursor != null)
                  if (cursor != null)
                  {
                      cursor.Close();
                  }
          }
          return null;
      }
    

Build the path:

string fileName = getFileName(context, uri);
string filePath = Android.OS.Environment.ExternalStorageDirectory + "/Download/" + fileName;
Related