6

Suppose a Uri can be either of the following:

  • A Uri from a Storage Access Framework's DocumentFile (i.e. DocumentFile.getUri()).
  • A Uri from a regular File (i.e. Uri.fromFile(File))

It refers to a file under a directory in both situations.

Is there a straightforward way to get its parent directory's Uri without trying each of the two to see which one works?

[Edit]: Here is an example for SAF:

Uri:

content://com.android.externalstorage.documents/tree/0000-0000%3Atest/document/0000-0000%3Atest%2Ffoo%2FMovies%2FRR%20parking%20lot%20a%202018_02_22_075101.mp4

getPath():

/tree/0000-0000:test/document/0000-0000:test/foo/Movies/RR parking lot a 2018_02_22_075101.mp4

getPathSegments():

0 = "tree"
1 = "0000-0000:test" 
2 = "document"
3 = "0000-0000:test/foo/Movies/RR parking lot a 2018_02_22_075101.mp4"

The parent folder should be test/foo/Movies.

Here is an example for a regular file:

Uri:

file:///storage/emulated/0/foo/Movies/RR%20parking%20lot%20a%202018_02_22_081351.mp4

getPath():

/storage/emulated/0/foo/Movies/RR parking lot a 2018_02_22_081351.mp4

getPathSegments():

0 = "storage"
1 = "emulated"
2 = "0"
3 = "foo"
4 = "Movies"
5 = "RR parking lot a 2018_02_22_081351.mp4"
Hong
  • 17,643
  • 21
  • 81
  • 142
  • You are assuming, that Android Uri has to be associated with some kind of parent directory, — this is not true. Uri does not need a parent, it does not even need to be associated with a file. A Storage Access Framework's Uri may refer to blob, stored in SQLite database. In such case there is no parent "directory" to speak of, that Uri would be associated directly with the "storage root" of that DocumentsProvider. Furthermore, non-document Uris (Uris from older ContentProvider infrastructure) don't even have concept of hierarchy They are just opaque strings, that *may* refer to some bytestream – user1643723 Feb 22 '18 at 04:58
  • Thank you for the explanation. I understood it. Sorry for the ambiguity. I edited the question to make it clear it refers to a file under a directory in both situations. – Hong Feb 22 '18 at 11:34
  • @Hong What have your tried? Given you're working with know nfile URIs, I'd think [`getPath()`](https://developer.android.com/reference/android/net/Uri.html#getPath()) or [getPathSegments()](https://developer.android.com/reference/android/net/Uri.html#getPathSegments()) would be a good start. – Andrew Henle Feb 22 '18 at 11:39
  • @AndrewHenle Per your request, I added an example to the question because it is impossible to do it in a comment. Are you suggesting getting the parent Uri by parsing the file Uri? If so, is this reliable across all versions of Android? – Hong Feb 22 '18 at 13:10
  • @Hong From what you posted, you're going to have to do some parsing on what you get. *is this reliable across all versions of Android?* I'm not sure that there's any one person alive who can answer that question. "Everything to the left of the last `'/'` character" might be the best you can do. – Andrew Henle Feb 22 '18 at 13:24
  • @AndrewHenle If you look at the Uri of the SAF example, its construction is not as simple as authority + getPath(). Some ":" and "/" are % encoded, and some are not. I do not know what tricks Android is playing with all these messy encoding. This makes me leery of reading into the SAF's Uri too much. – Hong Feb 22 '18 at 14:31
  • @AndrewHenle "I'm not sure that there's any one person alive who can answer that", — the format has already changed in the path (with tree Uris) and creators of Storage Access Framework have repeatedly warned not to rely on it. Besidese, when symlinks are used, the parent of file is not necessarily the same thing as part of path to the left of last slash. – user1643723 Feb 26 '18 at 03:10

1 Answers1

2

3 years late, I had a similar problem. I've resolved it using the Android 26 API DocumentsContract.findDocumentPath. It's OK for me since I use File API on earlier Android versions in my project.

Essentially, I find the document's path using findDocumentPath and remove the last segment from it (i.e. find the parent directory's path). Then, to reform the SAF Uri, I find the last / or : character in the Uri and replace the next part with parent directory's path.

public static String GetParentDirectory( ContentResolver resolver, String rawUri )
{
    if( !rawUri.contains( "://" ) )
    {
        // This is a raw filepath, not a SAF path
        return new File( rawUri ).getParent();
    }
    
    // Calculate the URI's path using findDocumentPath, omit the last path segment from it and then replace the rawUri's path component entirely
    DocumentsContract.Path rawUriPath = DocumentsContract.findDocumentPath( resolver, Uri.parse( rawUri ) );
    if( rawUriPath != null )
    {
        List<String> pathSegments = rawUriPath.getPath();
        if( pathSegments != null && pathSegments.size() > 0 )
        {
            String rawUriParentPath;
            if( pathSegments.size() > 1 )
                rawUriParentPath = Uri.encode( pathSegments.get( pathSegments.size() - 2 ) );
            else
            {
                String fullPath = pathSegments.get( 0 );
                int separatorIndex = Math.max( fullPath.lastIndexOf( '/' ), fullPath.lastIndexOf( ':' ) + 1 );
                rawUriParentPath = separatorIndex > 0 ? Uri.encode( fullPath.substring( 0, separatorIndex ) ) : null;
            }

            if( rawUriParentPath != null && rawUriParentPath.length() > 0 )
            {
                int rawUriLastPathSegmentIndex = rawUri.lastIndexOf( '/' ) + 1;
                if( rawUriLastPathSegmentIndex > 0 )
                {
                    String rawUriParent = rawUri.substring( 0, rawUriLastPathSegmentIndex ) + rawUriParentPath;
                    if( !rawUriParent.equals( rawUri ) )
                        return rawUriParent;
                }
            }
        }
    }

    return null;
}
yasirkula
  • 591
  • 2
  • 8
  • 47
  • 1
    Quick note: the call to DocumentsContract.findDocumentPath is looking for a Uri tree. Calling: DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri)); returns the uri tree that will allow this method to work. – foolioJones May 19 '22 at 07:55