How do you implement a FileObserver from an Android Service

45,175

Solution 1

Please see this post. I think you are missing the observer.startWatching() call after you setup your observer.

 observer = new FileObserver(pathToWatch) { // set up a file observer to watch this directory on sd card

     @Override
     public void onEvent(int event, String file) {
         //if(event == FileObserver.CREATE && !file.equals(".probe")){ // check if its a "create" and not equal to .probe because thats created every time camera is launched
         Log.d(TAG, "File created [" + pathToWatch + file + "]");

         Toast.makeText(getBaseContext(), file + " was saved!", Toast.LENGTH_LONG).show();
         //}
     }
 };
 observer.startWatching(); //START OBSERVING 

Solution 2

Add .show() after toast, i.e.

Toast.makeText(getBaseContext(), file + " was saved!", toast.LENGTH_LONG).show();                  

Solution 3

Here is the full code to create a service that listen for new file in a directory.

Firstly, you need to create the service that listen for new file entry in the directory. (E.g Camera)

MediaListenerService.java


import android.app.Service;
import android.content.Intent;
import android.os.FileObserver;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.io.File;

    public class MediaListenerService extends Service {

        public static FileObserver observer;

        public MediaListenerService() {
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }

        @Override
        public void onCreate() {
            super.onCreate();
            startWatching();
        }

        private void startWatching() {

        //The desired path to watch or monitor
        //E.g Camera folder
            final String pathToWatch = android.os.Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/";
            Toast.makeText(this, "My Service Started and trying to watch " + pathToWatch, Toast.LENGTH_LONG).show();

            observer = new FileObserver(pathToWatch, FileObserver.ALL_EVENTS) { // set up a file observer to watch this directory
                @Override
                public void onEvent(int event, final String file) {
                    if (event == FileObserver.CREATE || event == FileObserver.CLOSE_WRITE || event == FileObserver.MODIFY || event == FileObserver.MOVED_TO && !file.equals(".probe")) { // check that it's not equal to .probe because thats created every time camera is launched
                        Log.d("MediaListenerService", "File created [" + pathToWatch + file + "]");

                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getBaseContext(), file + " was saved!", Toast.LENGTH_LONG).show();


                            }
                        });
                    }
                }
            };
            observer.startWatching();
        }
    }

Next step, you need to Declare the service in AndroidManifest.xml inside tag

<service
    android:name=".service.MediaListenerService"
    android:enabled="true"
    android:exported="false" >
</service>

And also don't forget to add a permission:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

If you're writing for Android 6 or above, you'll need to request the permission dynamically too, as per these instructions: https://developer.android.com/training/permissions/requesting

Now start the service from your Activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    startService(new Intent(getBaseContext(), MediaListenerService.class));
}

If you want to make your service started on boot, just simply create a receiver that listen to android.intent.action.BOOT_COMPLETED and then launch the service from that.

Hope this helps.

Solution 4

One more thing FileObserver doesn't observe sub directory. If you want to observe sub-directories too Check out this post.

An open-source RecursiveFileObserver acts as advanced FileObserver that is recursive for all directories beneath the directory you chose

package com.owncloud.android.utils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import android.os.FileObserver;

public class RecursiveFileObserver extends FileObserver {

    public static int CHANGES_ONLY = CLOSE_WRITE | MOVE_SELF | MOVED_FROM;

    List<SingleFileObserver> mObservers;
    String mPath;
    int mMask;

    public RecursiveFileObserver(String path) {
        this(path, ALL_EVENTS);
    }

    public RecursiveFileObserver(String path, int mask) {
        super(path, mask);
        mPath = path;
        mMask = mask;
    }

    @Override
    public void startWatching() {
        if (mObservers != null) return;
        mObservers = new ArrayList<SingleFileObserver>();
        Stack<String> stack = new Stack<String>();
        stack.push(mPath);

        while (!stack.empty()) {
            String parent = stack.pop();
            mObservers.add(new SingleFileObserver(parent, mMask));
            File path = new File(parent);
            File[] files = path.listFiles();
            if (files == null) continue;
            for (int i = 0; i < files.length; ++i) {
                if (files[i].isDirectory() && !files[i].getName().equals(".")
                    && !files[i].getName().equals("..")) {
                    stack.push(files[i].getPath());
                }
            }
        }
        for (int i = 0; i < mObservers.size(); i++)
            mObservers.get(i).startWatching();
    }

    @Override
    public void stopWatching() {
        if (mObservers == null) return;

        for (int i = 0; i < mObservers.size(); ++i)
            mObservers.get(i).stopWatching();

        mObservers.clear();
        mObservers = null;
    }

    @Override
    public void onEvent(int event, String path) {

    }

    private class SingleFileObserver extends FileObserver {
        private String mPath;

        public SingleFileObserver(String path, int mask) {
            super(path, mask);
            mPath = path;
        }

        @Override
        public void onEvent(int event, String path) {
            String newPath = mPath + "/" + path;
            RecursiveFileObserver.this.onEvent(event, newPath);
        } 

    }
}

Source on GitHub

Share:
45,175
shanabus
Author by

shanabus

I learn a lot from using Stackoverflow and enjoy contributing when I can. Most development is in .NET (C#) but I also enjoy fiddling with Android and JavaScript frameworks.

Updated on July 09, 2022

Comments

  • shanabus
    shanabus almost 2 years

    How do you structure an Android app to start a Service to use a FileObserver so that when the observed directory is modified (ie user takes picture) some other code executes. When debugging, the onEvent method is never triggered.

    Here is the onStart event I have in my Service. The Toast fires for "My Service Started..."

    public final String TAG = "DEBUG";
    public static FileObserver observer;    
    
    @Override
    public void onStart(Intent intent, int startid) {       
            Log.d(TAG, "onStart");
    
            final String pathToWatch = android.os.Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/";       
            Toast.makeText(this, "My Service Started and trying to watch " + pathToWatch, Toast.LENGTH_LONG).show();
    
            observer = new FileObserver(pathToWatch) { // set up a file observer to watch this directory on sd card
                @Override
                public void onEvent(int event, String file) {
                    //if(event == FileObserver.CREATE && !file.equals(".probe")){ // check if its a "create" and not equal to .probe because thats created every time camera is launched
                        Log.d(TAG, "File created [" + pathToWatch + file + "]");
    
                        Toast.makeText(getBaseContext(), file + " was saved!", Toast.LENGTH_LONG);                  
                    //}
                }
            };
        }
    

    But after that Toast, if I take a picture the onEvent never fires. This is determined by debugging. It never hits that breakpoint and the Toast never fires.

    When that directory is browsed, the new image is saved there.

    How do you get a FileObserver working in a Service?