How to replace FileObserver in Android 10?
Asked Answered
W

3

9

android.os.FileObserver requires a java.io.File to function. But with Android 10 Google restricted access to everything but your app's private directory due to the famous "Storage Access Framework". Thus, accessing anything via java.io.File breaks and renders FileObserver useless unless you intend to use it in your app's private directory. However, I want to be notified when something is changed in a certain directory on external storage. I would also like to avoid periodically checking for changes.

I tried using ContentResolver.registerContentObserver(uri,notifyForDescendants,observer) and ran into some problems with that method:

  • Every Uri I have plugged in so far was accepted
  • It neither fails nor notifies if the Uri doesn't work
  • I cannot find any documentation telling me which Uris actually work

The only thing I got working to some extent is the following approach:

// works, but returns all changes to the external storage
contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, contentObserver)

Unfortunately this includes all of the external storage and only returns Media Uris when changes happen - for example content://media/external/file/67226.

Is there a way to find out whether or not that Uri points to my directory?

Or is there a way to make registerContentObserver() work with a Uri in such a way that I get a notification whenever something in the folder has changed?

I also had no success trying various Uris related to DocumentsFile and external storage Uris.

Wore answered 11/11, 2019 at 1:16 Comment(5)
maybe try passing the directory uri instead of External Conten Uri.Gnash
I should have mentioned but that didn't work.Wore
@Wore were you able to resolve the issue. I am working on similar task and stuck on this.Loren
@Wore did you resolved this issue? i'm getting stuck on itHeadway
Problem persists.Deferred
I
1

I kept getting errors when even trying to use the base constructor such as the following -

No direct method <init>(Ljava/util/List;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar!classes2.dex)

From a comment on Detect file change using FileObserver on Android:

I saw that message (or something like that) when i was trying to use constructor FileObserver(File). Use of deprecated FileObserver(String) solved my problem.... Original FileObserver has bugs.

Full disclosure, I was using the Xamarin.Android API; however, the gist and the commenter I quoted were both working with Java. At any rate, indeed - tried again using the counterpart String constructor and I was finally able to make and use the observer. Grinds my gears to use a deprecated API, but apparently they're hanging onto it at least up to and including Android 12.0.0_r3... still, would much prefer the supported constructors actually work. Maybe there's some warrant here for filing an issue.

Inflation answered 20/10, 2021 at 21:39 Comment(1)
The counterpart String constructor works for me too.Deferred
E
-1

I found a way to implement FileObserver on Android 10 with ContentObserver, but it might only work with media files since it works with media content uris.

The uri for ContentResolver.registerContentObserver() should be the file's corresponding media uri (e.g. content://media/external/file/49) which is queried by file path.

fun getMediaUri(context: Context, file: File): Uri? {
    val externalUri = MediaStore.Files.getContentUri("external")
    context.contentResolver.query(
        externalUri,
        null,
        "${MediaStore.Files.FileColumns.DATA} = ?",
        arrayOf(file.path),
        null
    )?.use { cursor ->
        if (cursor.moveToFirst()) {
            val idIndex = cursor.getColumnIndex("_id")
            val id = cursor.getLong(idIndex)
            return Uri.withAppendedPath(externalUri, "$id")
        }
    }
    return null
}

Then ContentObserver.onChange() will be triggered for every file change with uri: content://media/external/file/{id}; uri in ContentObserver.onChange(boolean selfChange, Uri uri) will always be content://media/external/file; only registered file will be with id (e.g. content://media/external/file/49?deletedata=false).

Does what FileObserver used to do when input uri's path matches registered uri.

Eviscerate answered 6/1, 2020 at 10:15 Comment(0)
H
-2

I have a temporary solution for this issue, so let's see if this can help. I start an infinite while loop watching for file created and file deleted (if you want file modified or file renamed you have to implement more) using DocumentFile. Below is my sample:

private static int currentFileIndirectory = 0;
private static final int FILE_CREATED = 0;
private static final int FILE_DELETED = 1;

private static DocumentFile[] onDirectoryChanged(DocumentFile[] documentFiles, int event) {
    Log.d("FileUtil", "onDirectoryChanged: " + event);
    if (event == FILE_CREATED) {
    } else {

    }
    return documentFiles;
}

private static boolean didStartWatching = false;

private static void startWatchingDirectory(final DocumentFile directory) {
    if (!didStartWatching) {
        didStartWatching = true;
        DocumentFile[] documentFiles = directory.listFiles();
        if (null != documentFiles && documentFiles.length > 0) {
            currentFileIndirectory = documentFiles.length;
        }

        new Thread(new Runnable() {
            public void run() {
                while (true) {
                    DocumentFile[] documentFiles = directory.listFiles();
                    if (null != documentFiles && documentFiles.length > 0) {
                        if (documentFiles.length != currentFileIndirectory) {
                            if (documentFiles.length > currentFileIndirectory) {//file created
                                DocumentFile[] newFiles = new DocumentFile[documentFiles.length - currentFileIndirectory];
                                onDirectoryChanged(newFiles, FILE_CREATED);
                            } else {//file Deleted
                                onDirectoryChanged(null, FILE_DELETED);
                            }
                            currentFileIndirectory = documentFiles.length;
                        }
                    }
                }
            }
        }).start();
    }
} 
Headway answered 4/11, 2020 at 9:37 Comment(1)
This is a poor implementation - it introduces a strenuously polling loop, and does not actually provide the onDirectoryChanged callback any of the new files, just an empty array sized to the number of new files. Might as well change the documentFiles parameter to an int.Inflation

© 2022 - 2024 — McMap. All rights reserved.