javaandroidandroid-studioandroid-contentproviderfileobserver

Android | contentObserver | Content URI does not contain the resource ID


I am trying to detect screenshot on an Android app. I am using contentObserver to detect the change in the media directory, not using FileObserver because of known issue with it on Android M.

Here is the code snippet:

    handlerThread = new HandlerThread("content_observer");
    handlerThread.start();
    final Handler handler = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    getContentResolver().registerContentObserver(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            true,
            new ContentObserver(handler) {
                @Override
                public void onChange(boolean selfChange, Uri uri) {
                    Log.d(TAG, "URL : " + uri.toString());

                    //Code to query using content resolver
                    if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {

                        Cursor cursor = null;
                        try {
                            cursor = getContentResolver().query(uri, new String[] {
                                    MediaStore.Images.Media.DISPLAY_NAME,
                                    MediaStore.Images.Media.DATA
                            }, null, null, null);
                            if (cursor != null && cursor.moveToFirst()) {          
                             //Logic to filter if it is a screenshot file
                            }
                        } finally {
                            if (cursor != null)  {
                                cursor.close();
                            }
                        }
                    }
                    super.onChange(selfChange, uri);
                }
            }
    );

When onChange is called (after screenshot is taken / any other changes in media folder) the URI is "content://media/external/images/media" instead of the whole URI containing the resource ID as well, something like: content://media/external/images/media/12345, this happens for most of the devices I tried - Moto G3 (Android 6.0.1), Nexus 5 (Android 6.0), where as, I get the whole URI (including ID) on Samsung S5 running 6.0.1.

Is this is a known issue?

How can I get the ID as well?

It is not an optimized solution to iterate over the whole media directory and find if a screenshot was taken when the app was active, rather getting the resource ID in the content URI would solve the problem as we can directly fetch the file's path and detect if it is a screenshot file, the observer still observes for all the media content changes though (for example, it also observes a WhatsApp image download happening in the background).


Solution

  • Unfortunately, I think you won't be able to count on receiving correct Uri upon insertions into tables specified in MediaStore.

    This is the source of MediaProvider#insert() method for Android 5.1.1 (taken from here):

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        int match = URI_MATCHER.match(uri);
    
        ArrayList<Long> notifyRowIds = new ArrayList<Long>();
        Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
        notifyMtp(notifyRowIds);
    
        // do not signal notification for MTP objects.
        // we will signal instead after file transfer is successful.
        if (newUri != null && match != MTP_OBJECTS) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return newUri;
    }
    

    Note this line: getContext().getContentResolver().notifyChange(uri, null).

    The above means that upon insertion into MediaProvider, notifications will be send for Uri specified for insertion, which is always a "directory" Uri. This is, probably, a bug: if instead of uri they would use newUri - it would work exactly as you expected it to work.

    If it works on Samsung with Marshmallow, then either this was fixed in Android 6+, or Samsung fixed it in their build of AOSP. But I don't think that you can really count on this to work reliably.