4

Hi i am trying to refresh gallery by initiating a scan. For devices running Kitkat and higher versions i am using mediascannerconnection. But the mediaScannerConnection needs absolute path to do scan. What i have is a DocumentFile which does not seem to have any method to get the absolute path of it.

Below is the code i am using:

To get permission to write to the folder using the new lollipop API-->Saving the selected URI to a global variable---> Logic to hide(create/delete ".nomedia" file).

// Calling folder selector.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);
..............
@SuppressLint("NewApi") @Override
    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
        if (resultCode == RESULT_OK && requestCode == 42) {
            Uri treeUri = resultData.getData();
            getContentResolver().takePersistableUriPermission(treeUri,
//                  Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
                    Intent.FLAG_GRANT_READ_URI_PERMISSION |             
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            path = treeUri.getPath();
        }

 }
 ..........
 public void hide(boolean hide){
    DocumentFile pickedDir = DocumentFile.fromTreeUri(this,Uri.parse(path));
    if(hide){
            if(pickedDir.findFile(".nomedia")==null){
                pickedDir.createFile(null, ".nomedia");
                tellgallery(pickedDir.findFile(".nomedia").getUri().getPath());
            }
    }
    else{
            DocumentFile filetodelete = pickedDir.findFile(".nomedia");
            if(filetodelete!=null){
                filetodelete.delete();
                tellgallery(filetodelete.getPath());
            }
    }
 }

This code above works perfectly and creates/deletes a ".nomedia" file in the desired folder.

Now when i try to tell the gallery that files have changed for this folder. Gallery is not able to pick the change.

Below is the code to refresh/request the scan.

private void tellgallery(String path) {
    pathtonomedia = path;
    if(path!=null){
    if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        mediaScannerConnection = new MediaScannerConnection(con,mediaScannerConnectionClient);
        mediaScannerConnection.connect();
    }
      else{
            this.sendBroadcast(new Intent(
            Intent.ACTION_MEDIA_MOUNTED,
            Uri.parse("file://" + path)));
      }
    }

}

MediaScanner class is as below:

private MediaScannerConnectionClient mediaScannerConnectionClient = 
        new MediaScannerConnectionClient() {

        @Override
        public void onMediaScannerConnected() {
            mediaScannerConnection.scanFile(pathtonomedia, null);              
        }

        @Override
        public void onScanCompleted(String path, Uri uri) {
            if(path.equals(pathtonomedia))
                mediaScannerConnection.disconnect();

        }
    };

But the code does not refresh the gallery and i can still see the folder in gallery. The folder however vanishes after sometime like 10 to 20 mins may be by system refresh.

Finally what i want is

  • Is there anyway i can get path of the DocumentFile instance?. So that i can just directly pass it to the mediascanner path

  • Or else is there any other method by which we can request a scan on specific folder in lollipop.The specific folder being in SDCARD and i know only the URI to it not the actual path.

Any help is highly appreciated.

Grzegorz Adam Hankiewicz
  • 7,349
  • 1
  • 36
  • 78
Gaurav Pangam
  • 332
  • 3
  • 8

2 Answers2

3

As you have noticed deletions don't trigger a media scan, and the DocumentFile Uri doesn't work for the MediaScanner, so before deleting a file you have to query the MediaScanner for a valid path. What I do is iterate through all the DocumentFile objects and call .getLastPathSegment() on them. This returns a value like:

E25A-3848:Pictures/appname/images/foo.jpg

In my tests this value always has a colon separating some device specific value from the tail of the path. If the user picks an internal storage instead of E25A-3838 they will get a word like primary, but that's not relevant. What's important is that you want to get whatever is behind the colon, so I iterate like this through the documents I need to delete:

String temp = document.getUri().getLastPathSegment();
final int lastColon = temp.lastIndexOf(":");
if (lastColon > 0) {
    boolean deletionSuccess = document.delete();
    if (deletionSuccess) {
        Log.d(TAG, "Deleted " + document.getUri());
        segmentsToDelete.add(temp.substring(lastColon + 1));
    }
}

After this loop the documents will be gone and the segmentsToDelete array list will contain all the trailing paths that need to be purged from the MediaScanner. So I do another loop asking the MediaScanner to delete all the entries which end with the same value:

final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
for (String lastPathSegment : segmentsToDelete) {
    final int result = contentResolver.delete(uri,
        MediaStore.MediaColumns.DATA + " LIKE ? ESCAPE '\\'",
        new String[]{"%" + escapedLike(lastPathSegment)}
    );
    Log.d(TAG, "Media deletion for " + lastPathSegment + " was " + result);
}

The .delete() call will return the number of deleted rows, so you can put there an assertion to make sure it always returns 1. May return less if for some reason the document never made it into the mediascanner though. The escapedLike() method is a custom function used to make sure the SQLITE like pattern matching doesn't match the incorrect paths:

static String escapedLike(@NonNull String text)
{
    return text.replace("%", "\\%").replace("_", "\\_");
}

Of course the above works the same if you want to delete a whole directory. In such case you don't need to run loops, you can first call .delete() on the DocumentFile for the directory, and then when you build the deletion query you use a trailing % for the sql LIKE parameter which should return a lot of deletions (as many as files were registered in the media scanner).

The reason LIKE matches are used instead of exact full paths is because the previous value obtained from the DocumentFile will never match a full path. Something like E25A-3848:Pictures/appname/images/foo.jpg will later be returned by the MediaScanner database like /storage/sdcard1/Pictures/appname/images/foo.jpg. It's a shame the deletion doesn't invoke the MediaScanner internally…

Grzegorz Adam Hankiewicz
  • 7,349
  • 1
  • 36
  • 78
0

Have you tried this? This works in many cases. It works for me after calling a MediaScannerConnection, and this should scan the entire SD Card.

    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://" + Environment.getExternalStorageDirectory())));
pancodemakes
  • 564
  • 7
  • 26
  • Yes i am currently using this in my production version, but this does not work in versions above kitkat. As sending broadcast is not allowed in new versions.. – Gaurav Pangam Nov 02 '15 at 16:02