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.
 

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
Upvote 0

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