Android SAF (Storage Access FrameWork): Get particular file Uri from TreeUri

Access Sd-Card’s files

Use DOCUMENT_TREE dialog to get sd-card’s Uri.

Inform the user about how to choose sd-card on the dialog. (with pictures or gif animations)

// call for document tree dialog
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);

On onActivityResult you’ll have the selected directory Uri. (sdCardUri)

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case REQUEST_CODE_OPEN_DOCUMENT_TREE:
            if (resultCode == Activity.RESULT_OK) {
                sdCardUri = data.getData();
             }
             break;
     }
  }

Now must check if the user,

a. selected the sd-card

b. selected the sd-card that our file is on (some devices could have multiple sd-cards).


We check both a and b by finding the file through the hierarchy, from sd root to our file. If the file found, both of a and b conditions are acquired.

//First we get `DocumentFile` from the `TreeUri` which in our case is `sdCardUri`.
DocumentFile documentFile = DocumentFile.fromTreeUri(this, sdCardUri);

//Then we split file path into array of strings.
//ex: parts:{"", "storage", "extSdCard", "MyFolder", "MyFolder", "myImage.jpg"}
// There is a reason for having two similar names "MyFolder" in 
//my exmple file path to show you similarity in names in a path will not 
//distract our hiarchy search that is provided below.
String[] parts = (file.getPath()).split("\\/");

// findFile method will search documentFile for the first file 
// with the expected `DisplayName`

// We skip first three items because we are already on it.(sdCardUri = /storage/extSdCard)
for (int i = 3; i < parts.length; i++) {
    if (documentFile != null) {
        documentFile = documentFile.findFile(parts[i]);
    }
  }

if (documentFile == null) {

    // File not found on tree search
    // User selected a wrong directory as the sd-card
    // Here must inform the user about how to get the correct sd-card
    // and invoke file chooser dialog again.  

    // If the user selects a wrong path instead of the sd-card itself,  
    // you should ask the user to select a correct path.  
    // I've developed a gallery app with this behavior implemented in it.  
    // https://play.google.com/store/apps/details?id=com.majidpooreftekhari.galleryfarsi
    // After you installed the app, try to delete one image from the  
    // sd-card and when the app requests the sd-card, select a wrong path  
    // to see how the app behaves.  

 } else {

    // File found on sd-card and it is a correct sd-card directory
    // save this path as a root for sd-card on your database(SQLite, XML, txt,...)

    // Now do whatever you like to do with documentFile.
    // Here I do deletion to provide an example.


    if (documentFile.delete()) {// if delete file succeed 
        // Remove information related to your media from ContentResolver,
        // which documentFile.delete() didn't do the trick for me. 
        // Must do it otherwise you will end up with showing an empty
        // ImageView if you are getting your URLs from MediaStore.
        // 
        Uri mediaContentUri = ContentUris.withAppendedId(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                longMediaId);
        getContentResolver().delete(mediaContentUri , null, null);
    }


 }

Wrong sd-card path selection behavior on my app:

To check the behavior on the wrong sd-card path selection install the app and try to delete an image that is on your sd-card and select a wrong path instead of your sd-card directory.
Calendar Gallery: https://play.google.com/store/apps/details?id=com.majidpooreftekhari.galleryfarsi

Note:

You must provide access permission to the external storage inside your manifest and for os>=Marshmallow inside the app.
https://stackoverflow.com/a/32175771/2123400


Edit Sd-Card’s Files

For editing an existing image on your sd-card you don’t need any of the above steps if you want to invoke another app to do it for you.

Here we invoke all the activities (from all the installed apps) with the capability of editing the images. (Programmers mark their apps in the manifest for its capabilities to provide accessibility from other apps (activities)).

on your editButton click event:

String mimeType = getMimeTypeFromMediaContentUri(mediaContentUri);
startActivityForResult(Intent.createChooser(new Intent(Intent.ACTION_EDIT).setDataAndType(mediaContentUri, mimeType).putExtra(Intent.EXTRA_STREAM, mediaContentUri).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION), "Edit"), REQUEST_CODE_SHARE_EDIT_SET_AS_INTENT);

and this is how to get mimeType:

public String getMimeTypeFromMediaContentUri(Uri uri) {
    String mimeType;
    if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
        ContentResolver cr = getContentResolver();
        mimeType = cr.getType(uri);
    } else {
        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
                .toString());
        mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
                fileExtension.toLowerCase());
    }
    return mimeType;
}

Note:

On Android KitKat(4.4) don’t ask the user to select the sd-card because on this version of Android DocumentProvider is not applicable, hence we have no chance to have access to the sd-card with this approach.
Look at the API level for the DocumentProvider
https://developer.android.com/reference/android/provider/DocumentsProvider.html
I couldn’t find anything that works on Android KitKat(4.4). If you found anything useful with KitKat please share it with us.

On versions below the KitKat access to sd-card is already provided by the OS.

Leave a Comment