androiduriabsolute-pathdocumentfile

opening an external "file explorer" app: how to get absolute path from a uri pointing to a folder


In my app, the user can choose where the created files (text files) are created. This part is working fine. But now, I want to open an external "file explorer" app, pointing directly to the chosen folder. The "file explorer " apps I know accept an absolute path as input (like /storage/emulated/0/Documents/test_folder)

When the user chooses a folder (with Intent.ACTION_OPEN_DOCUMENT_TREE), I get a content uri (like content://com.android.externalstorage.documents/tree/home%3Atest_folder)

Another example with an external sd card:

The uri points to a folder, not a file, so I can't use something like openInputStream

I have tried :

File f = new File(uri.getPath());
String path = f.getAbsolutePath();

but it gives: /tree/home:test_folder or /tree/3877-DB74:Documents/test_folder if on sd card

How can I get the real absolute path?

The code I use to call a file explorer:

Intent intent = new Intent(Intent.ACTION_VIEW);
String path = getExternalFilesDir(null).getAbsolutePath();
intent.setDataAndType(Uri.parse(path), "resource/folder");
if (intent.resolveActivityInfo(getPackageManager(), 0) != null)
{
    startActivity(intent);
}

Solution

  • I finally wrote my own method to get the absolute path for a folder from a Uri. It is surely not fully generic, but it meets my need.

    if it can help someone, here is my code:

    Note: VOLUME_MAP is a map containing all mounted external volumes

        /**************************************************************************/
        public static String getRealPathFromContentUri(final Uri uri)
        {
            if (!isExternalStorageDocument(uri))
            {
                return null;
            }
    
            List<String> segs = uri.getPathSegments();
            if (!"tree".equalsIgnoreCase(segs.get(0)))
            {
                return null;
            }
    
            String path = uri.getLastPathSegment();
            final String[] split = path.split(":");
            final String volumeId = split[0];
            String userPath = "";
            if (split.length > 1)
            {
                userPath = "/" + split[1];
            }
            if ("primary".equalsIgnoreCase(volumeId))
            {
                return Environment.getExternalStorageDirectory().getAbsolutePath() + userPath;
            }
    
            if ("home".equalsIgnoreCase(volumeId))
            {
                return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + userPath;
            }
    
            // look for real volumeId
            final String volumeName = VOLUME_MAP.get(volumeId);
            if (volumeName == null)
            {
                return null;
            }
            path = "/storage";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
            {
                path = Environment.getStorageDirectory().getAbsolutePath();
            }
            return path  + "/" + volumeId + userPath;
        }
    

    Here is how to get VOLUME_MAP:

    private static Map<String, String> initVolumeMap()
    {
        final Map<String, String> volumeMap = new HashMap<>();
        volumeMap.put("primary", null); 
        volumeMap.put("home", "Documents"); 
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && PadocApp.getInstance() != null)
        {
            final Context context = MyApp.getInstance().getApplicationContext();
            final StorageManager storageManager = ContextCompat.getSystemService(context, StorageManager.class);
            assert storageManager != null;
            final List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
            for (StorageVolume sv : storageVolumes)
            {
                volumeMap.put(sv.getUuid(), sv.getDescription(context));
            }
        }
        else
        {
            //at least we create a default entry for the primary drive
            volumeMap.put(null, "internal shared storage");
        }
        return volumeMap;
    }
    

    Thanks to all contributors on this topic.