How do you implement a FileObserver from an Android Service
Asked Answered
E

4

31

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?

Excrescent answered 1/9, 2011 at 3:40 Comment(0)
V
54

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 
Vaclav answered 1/9, 2011 at 3:56 Comment(4)
it will be return file path properly but when i'm check simply file is exists or not it always return false means file exists but return false please help us!!September
Please edit this answer to use Handler to post the result as this will generate. Can't toast on a thread that has not called Looper.prepare()Ritaritardando
@mohnage7 I haven't done Android development in 8+ years, feel free to recommend an edit or post another answerVaclav
it does not working with noughat just at 4.0 and 5.0 does not work after 23 api.Mcdonough
T
9

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

Toast.makeText(getBaseContext(), file + " was saved!", toast.LENGTH_LONG).show();                  
Tinsmith answered 14/5, 2013 at 7:33 Comment(0)
P
9

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.

Puffball answered 27/6, 2018 at 12:7 Comment(2)
This doesn't work for me. The service starts successfully but the log message is never output.Lumbricalis
Apologies, this is because I didn't request the permission dynamically. I'm updating the answer to include this.Lumbricalis
B
5

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

Blurt answered 12/12, 2015 at 12:2 Comment(3)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewKazimir
Linked page is stackoverflow answer..so there is no needBlurt
Is this possible in fileobserver to get notified on New Folder (New Directory) ?Phenoxide

© 2022 - 2024 — McMap. All rights reserved.