Android Question How to get filename from URI (again)?

fredo

Well-Known Member
Licensed User
Longtime User

It is well known that there are good reasons, whenever possible, to use the "content-path" as it was provided.

For requirements where a file path is actually needed, solutions were offered here and here.

Erel: "In most cases it is a mistake to use this sub. Especially in newer versions of Android. You shouldn't assume that the resource returned from ContentChooser comes from the file system and if it is, you most probably won't have permissions to directly access it."

For a current project, it is necessary to determine the file name of a file that was selected with the ContentChooser. The file names were previously defined by other users and are relevant for further processing of the file.

Based on the solution of Erel and Inman the attached test project was created.
2018-12-14_16-00-55.png

B4X:
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.DarkTheme)
'End of default text.

B4X:
' Based on Erel and Inman --> https://www.b4x.com/android/forum/threads/how-to-get-full-path-from-uri.89974/#post-569010

#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
    #AdditionalJar: com.android.support:support-v4
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
End Sub

Sub Globals
    Private Label1 As Label
    Private Label2 As Label
    Private Button2 As Button
    Private Button4 As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout1")
    Label1.Text = ""
    Label2.Text = ""
    Button2.Visible = False
    Button4.Visible = False
End Sub

Sub Activity_Resume
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub

' ────────────────────────────────────────────────────────────────────────
Sub Button1_Click
    Label2.Visible = False
    Label2.Text = ""
    CallSubDelayed2(Me, "ChooseFile", "application")
End Sub
Sub Button3_Click
    Label2.Visible = False
    Label2.Text = ""
    CallSubDelayed2(Me, "ChooseFile", "image")
End Sub

Sub Button4_Click
    Label2.Visible = True
    Label2.Text = "...Trying GetPath(Label1.Text)"
    Label2.Text = GetPath(Label1.Text) ' <<<<<<<<<<<<<<<<------------------
End Sub
Sub Button2_Click
    Label2.Visible = True
    Label2.Text = "...Trying GetPathFromContentResult(Label1.Text)"
    Label2.Text = GetPathFromContentResult(Label1.Text)
End Sub
' ────────────────────────────────────────────────────────────────────────
Sub ChooseFile(Mime As String) As ResumableSub
    Label1.Text = "..."
    Dim cc As ContentChooser
    cc.Initialize("cc")
    cc.Show(Mime & "/*", "Choose a file")
    wait for cc_Result (Success As Boolean, Dir As String, FileName As String)
    Label1.Text = FileName   
    If Not(Success) Then Return Null
    Button2.Visible=True
    Button4.Visible=True
    Return Null
End Sub
' ────────────────────────────────────────────────────────────────────────
Sub GetPathFromContentResult(UriString As String) As String
    ' Erel --> https://www.b4x.com/android/forum/threads/get-the-path-to-media-files-returned-from-contentchooser.39313/
    If UriString.StartsWith("/") Then Return UriString 'If the user used a file manager to choose the image
    Dim Cursor1 As Cursor
    Dim Uri1 As Uri
    Dim Proj() As String = Array As String("_data")
    Dim cr As ContentResolver
    cr.Initialize("")
    If UriString.StartsWith("content://com.android.providers.media.documents") Then
        Dim i As Int = UriString.IndexOf("%3A")
        Dim id As String = UriString.SubString(i + 3)
        Uri1.Parse("content://media/external/images/media")
        Cursor1 = cr.Query(Uri1, Proj, "_id = ?", Array As String(id), "")
    Else
        Uri1.Parse(UriString)
        Cursor1 = cr.Query(Uri1, Proj, "", Null, "")
    End If
    Cursor1.Position = 0
    Dim res As String
    res = Cursor1.GetString("_data")
    Cursor1.Close
    Return res
End Sub
' ────────────────────────────────────────────────────────────────────────

Sub GetPath(uristring As String) As String
    ' Inman --> https://www.b4x.com/android/forum/threads/how-to-get-full-path-from-uri.89974/#post-569092
    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

#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

Unfortunately an error occurs when calling the function "GetPath":​

I/B4A(10490): ~e:main_getpath (java line: 535)
I/B4A(10490): ~e:java.lang.reflect.InvocationTargetException
I/B4A(10490): ~e: at java.lang.reflect.Method.invoke(Native Method)
I/B4A(10490): ~e: at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:131)
I/B4A(10490): ~e: at b4a.example.main._getpath(main.java:535)
I/B4A(10490): ~e: at b4a.example.main._button4_click(main.java:424)
I/B4A(10490): ~e: at java.lang.reflect.Method.invoke(Native Method)
I/B4A(10490): ~e: at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
I/B4A(10490): ~e: at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
I/B4A(10490): ~e: at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
I/B4A(10490): ~e: at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
I/B4A(10490): ~e: at android.view.View.performClick(View.java:6600)
I/B4A(10490): ~e: at android.view.View.performClickInternal(View.java:6577)
I/B4A(10490): ~e: at android.view.View.access$3100(View.java:781)
I/B4A(10490): ~e: at android.view.View$PerformClick.run(View.java:25967)
I/B4A(10490): ~e: at android.os.Handler.handleCallback(Handler.java:873)
I/B4A(10490): ~e: at android.os.Handler.dispatchMessage(Handler.java:99)
I/B4A(10490): ~e: at android.os.Looper.loop(Looper.java:210)
I/B4A(10490): ~e: at android.app.ActivityThread.main(ActivityThread.java:7046)
I/B4A(10490): ~e: at java.lang.reflect.Method.invoke(Native Method)
I/B4A(10490): ~e: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
I/B4A(10490): ~e: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:879)
I/B4A(10490): ~e:Caused by: java.lang.IllegalArgumentException: Invalid URI: content://com.android.providers.downloads.documents/document/693
I/B4A(10490): ~e: at android.provider.DocumentsContract.getTreeDocumentId(DocumentsContract.java:1014)
I/B4A(10490): ~e: at b4a.example.main$FileUtil.getVolumeIdFromTreeUri(main.java:758)
I/B4A(10490): ~e: at b4a.example.main$FileUtil.getFullPathFromTreeUri(main.java:685)
I/B4A(10490): ~e: ... 20 more
E/AndroidRuntime(10490): FATAL EXCEPTION: main
E/AndroidRuntime(10490): Process: b4a.example, PID: 10490
E/AndroidRuntime(10490): java.lang.reflect.InvocationTargetException
E/AndroidRuntime(10490): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(10490): at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:131)
E/AndroidRuntime(10490): at b4a.example.main._getpath(main.java:535)
E/AndroidRuntime(10490): at b4a.example.main._button4_click(main.java:424)
E/AndroidRuntime(10490): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(10490): at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
E/AndroidRuntime(10490): at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
E/AndroidRuntime(10490): at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
E/AndroidRuntime(10490): at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
E/AndroidRuntime(10490): at android.view.View.performClick(View.java:6600)
E/AndroidRuntime(10490): at android.view.View.performClickInternal(View.java:6577)
E/AndroidRuntime(10490): at android.view.View.access$3100(View.java:781)
E/AndroidRuntime(10490): at android.view.View$PerformClick.run(View.java:25967)
E/AndroidRuntime(10490): at android.os.Handler.handleCallback(Handler.java:873)
E/AndroidRuntime(10490): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(10490): at android.os.Looper.loop(Looper.java:210)
E/AndroidRuntime(10490): at android.app.ActivityThread.main(ActivityThread.java:7046)
E/AndroidRuntime(10490): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(10490): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
E/AndroidRuntime(10490): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:879)
E/AndroidRuntime(10490): Caused by: java.lang.IllegalArgumentException: Invalid URI: content://com.android.providers.downloads.documents/document/693
E/AndroidRuntime(10490): at android.provider.DocumentsContract.getTreeDocumentId(DocumentsContract.java:1014)
E/AndroidRuntime(10490): at b4a.example.main$FileUtil.getVolumeIdFromTreeUri(main.java:758)
E/AndroidRuntime(10490): at b4a.example.main$FileUtil.getFullPathFromTreeUri(main.java:685)
E/AndroidRuntime(10490): ... 20 more​

Although the Contentchooser offers media from many sources, it should be at least possible to get path information from locally stored files.

Could someone with extended system knowledge point out a practicable solution?
 

Attachments

  • FilenameFromContentchooser.zip
    12.9 KB · Views: 337

sorex

Expert
Licensed User
Longtime User
for B4J projects I use this with the file chooser

fd=file directory (path)
fn=filename

B4X:
 fn=fs.ShowOpen(MainForm)
 lblFile.Text=fn
 If fn="" Then Return
 fd=fn.SubString2(0,fn.LastIndexOf("\") )
 fn=fn.SubString(fn.LastIndexOf("\")+1 )

I don't know if this will work 100% on Android without some tweaking (like changing \ to /)
 
Upvote 0

josejad

Expert
Licensed User
Longtime User
Hi Fredo:

Not sure if it could be a permission problem?
Have you checked PERMISSION_READ_EXTERNAL_STORAGE?
 
Upvote 0

fredo

Well-Known Member
Licensed User
Longtime User
.... SubString(fn.LastIndexOf(..."

Thanks for the thought, but in this case it's an Android-specific problem where the content is not delivered by the file system.

Instead, the content is mediated by a ContentResolver and provided by the ContentProvider. The ContentProvider (thus the provider of the data) can be another app, an Android component, or a GDrive link etc..

Since the ContentChooser only receives a symbolic link from the provider's database, e.g. the name of a file from a file system is not directly available, but must be determined with some climbs from the provider database.
 
Upvote 0

fredo

Well-Known Member
Licensed User
Longtime User
..permission problem?

Okay, thanks, permissions are always worth a thought.

B4X:
AddManifestText(
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.DarkTheme)
'End of default text.

AddPermission("android.permission.WRITE_EXTERNAL_STORAGE")
AddPermission("android.permission.READ_EXTERNAL_STORAGE")

B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout1")
    Label1.Text = ""
    Label2.Text = ""
    Button2.Visible = False
    Button4.Visible = False
  
    If ShouldShowRequestPermissionRationale(rp.PERMISSION_READ_EXTERNAL_STORAGE) Then
        MsgboxAsync("We need permission to access the external storage in order to upload your personal photos to the cloud.", "")
        Wait For Msgbox_Result (Result As Int)
    End If
    rp.CheckAndRequest(rp.PERMISSION_READ_EXTERNAL_STORAGE)
    Wait For Activity_PermissionResult (Permission As String, Success As Boolean)
    If Success Then
        For Each f As String In File.ListFiles(File.DirRootExternal)
            Log(f)
        Next
    End If
  
End Sub



Didn't help...

 

Attachments

  • FilenameFromContentchooser_with_Permission_RWx.zip
    13.3 KB · Views: 323
Last edited:
Upvote 0
Top