Android Question How to get full path from URI?

Inman

Well-Known Member
Licensed User
Longtime User
Here is what I am trying to achieve. In my app there is an option for user to select a folder and save it so that a service will copy files into it in the background whenever a certain condition is met. So basically user selects the folder once and forgets it.

This folder can be in external SD card and so I cannot use the usual File.DirRootExternal to get the path and perform copy. Thanks to code shared in the community I was able to do it using using intent ACTION_OPEN_DOCUMENT_TREE.

Problem is it returns a URI and not a physical path like /storage/emulated/0/ etc... While I can perform copy options successfully with URI and streams (File.Copy2), I still need to display the full physical path to the folder user selected so that he can make sure the path is right. And URI path like content://com.android.externalstorage.documents/ won't make sense to him. It has to be in the format /storage/emulated/0/.

I tried the code posted here but it is crashing when testing on Android 7.0. Tested uri was (HierarchicalUri) content://com.android.externalstorage.documents/tree/primary%3ADCIM. URI is that of a folder on external SD card.

Check the lines below
B4X:
Cursor1 = cr.Query(Uri1, Proj, "", Null, "")
Log(Cursor1.RowCount)
The error comes at second line and this is because Cursor1 is not initiliazed as cr.Query from line above does not return anything.
B4X:
Error occurred on line: 222 (Test)

java.lang.RuntimeException: Object should first be initialized (Cursor).
    at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:50)
    at anywheresoftware.b4a.sql.SQL$CursorWrapper.getRowCount(SQL.java:335)
    at b4a.chrjak.sdcard.test._getpathfromcontentresult(test.java:661)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:710)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:339)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:170)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:166)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
    at android.view.View.performClick(View.java:5610)
    at android.view.View$PerformClick.run(View.java:22265)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6077)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
** Service (starter) Destroy **

Point to note is I am trying to get path from URI of a folder and not file. Not sure if that is what is causing the error.
 

Inman

Well-Known Member
Licensed User
Longtime User
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
You can add this code to your project with inline Java:
B4X:
#if JAVA
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.Log;

@SuppressLint("NewApi")
public static final class FileUtil {

    static String TAG="TAG";
    private static final String PRIMARY_VOLUME_NAME = "primary";



    public static boolean isKitkat() {
        return Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
    }
    public static boolean isAndroid5() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }


    @NonNull
    public static String getSdCardPath() {
        String sdCardDirectory = Environment.getExternalStorageDirectory().getAbsolutePath();

        try {
            sdCardDirectory = new File(sdCardDirectory).getCanonicalPath();
        }
        catch (IOException ioe) {
            Log.e(TAG, "Could not get SD directory", ioe);
        }
        return sdCardDirectory;
    }


    public static ArrayList<String> getExtSdCardPaths(Context con) {
        ArrayList<String> paths = new ArrayList<String>();
        File[] files = ContextCompat.getExternalFilesDirs(con, "external");
        File firstFile = files[0];
        for (File file : files) {
            if (file != null && !file.equals(firstFile)) {
                int index = file.getAbsolutePath().lastIndexOf("/Android/data");
                if (index < 0) {
                    Log.w("", "Unexpected external file dir: " + file.getAbsolutePath());
                }
                else {
                    String path = file.getAbsolutePath().substring(0, index);
                    try {
                        path = new File(path).getCanonicalPath();
                    }
                    catch (IOException e) {
                        // Keep non-canonical path.
                    }
                    paths.add(path);
                }
            }
        }
        return paths;
    }

    @Nullable
    public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
        if (treeUri == null) {
            return null;
        }
        String volumePath = FileUtil.getVolumePath(FileUtil.getVolumeIdFromTreeUri(treeUri),con);
        if (volumePath == null) {
            return File.separator;
        }
        if (volumePath.endsWith(File.separator)) {
            volumePath = volumePath.substring(0, volumePath.length() - 1);
        }

        String documentPath = FileUtil.getDocumentPathFromTreeUri(treeUri);
        if (documentPath.endsWith(File.separator)) {
            documentPath = documentPath.substring(0, documentPath.length() - 1);
        }

        if (documentPath.length() > 0) {
            if (documentPath.startsWith(File.separator)) {
                return volumePath + documentPath;
            }
            else {
                return volumePath + File.separator + documentPath;
            }
        }
        else {
            return volumePath;
        }
    }


    private static String getVolumePath(final String volumeId, Context con) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return null;
        }

        try {
            StorageManager mStorageManager =
                    (StorageManager) con.getSystemService(Context.STORAGE_SERVICE);

            Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");

            Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
            Method getUuid = storageVolumeClazz.getMethod("getUuid");
            Method getPath = storageVolumeClazz.getMethod("getPath");
            Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
            Object result = getVolumeList.invoke(mStorageManager);

            final int length = Array.getLength(result);
            for (int i = 0; i < length; i++) {
                Object storageVolumeElement = Array.get(result, i);
                String uuid = (String) getUuid.invoke(storageVolumeElement);
                Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);

                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                    return (String) getPath.invoke(storageVolumeElement);
                }

                // other volumes?
                if (uuid != null) {
                    if (uuid.equals(volumeId)) {
                        return (String) getPath.invoke(storageVolumeElement);
                    }
                }
            }

            // not found.
            return null;
        }
        catch (Exception ex) {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getVolumeIdFromTreeUri(final Uri treeUri) {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");

        if (split.length > 0) {
            return split[0];
        }
        else {
            return null;
        }
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getDocumentPathFromTreeUri(final Uri treeUri) {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");
        if ((split.length >= 2) && (split[1] != null)) {
            return split[1];
        }
        else {
            return File.separator;
        }
    }


}
#End If

Note that I've added 'static' to the class declaration.
You can call methods of this code with:
B4X:
Dim FileUtils As JavaObject
FileUtils.InitializeStatic(App.PackageName & ".main$FileUtil")

Try to call getFullPathFromTreeUri
 
Upvote 1

Inman

Well-Known Member
Licensed User
Longtime User
Thank you so much Erel. Your suggestion worked. Here is the code I used.
B4X:
Sub GetPath(uristring As String) As String
    Dim path As String
    Dim uri As Uri
    Dim ctxt As JavaObject
    Dim FileUtils As JavaObject
   
    ctxt.InitializeContext
    uri.Parse(uristring)
    FileUtils.InitializeStatic(Application.PackageName & ".main$FileUtil")
    path=FileUtils.RunMethod("getFullPathFromTreeUri", Array(uri,ctxt))
    Return path
End Sub
 
Upvote 0
Top