├── .gitignore ├── FileObserver ├── .cproject ├── .settings │ └── org.eclipse.cdt.core.prefs ├── AndroidManifest.xml ├── jni │ ├── Android.mk │ ├── Logger.h │ └── fileobserver_jni.cpp ├── libs │ ├── android-support-v4.jar │ └── armeabi │ │ ├── libFileObserver.so │ │ └── libfileobserver_jni.so ├── lint.xml ├── proguard-project.txt ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── values-v11 │ │ └── styles.xml │ ├── values-v14 │ │ └── styles.xml │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── custom │ └── fileobserver │ ├── FileListener.java │ ├── FileObserver.java │ ├── FileWatcher.java │ └── TestActivity.java ├── FileWatcher.java └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | -------------------------------------------------------------------------------- /FileObserver/.cproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /FileObserver/.settings/org.eclipse.cdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | environment/project/com.android.toolchain.gcc.2076706790/API-LEVEL/delimiter=; 3 | environment/project/com.android.toolchain.gcc.2076706790/API-LEVEL/operation=replace 4 | environment/project/com.android.toolchain.gcc.2076706790/API-LEVEL/value=16 5 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-1/delimiter=; 6 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-1/operation=append 7 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-1/value=${ANDROID_NDK_HOME}\\toolchains\\arm-linux-androideabi-4.6\\prebuilt\\windows\\lib\\gcc\\arm-linux-androideabi\\4.6.x-google\\include; 8 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-2/delimiter=; 9 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-2/operation=append 10 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-2/value=${ANDROID_NDK_HOME}\\toolchains\\arm-linux-androideabi-4.6\\prebuilt\\windows\\lib\\gcc\\arm-linux-androideabi\\4.6.x-google\\include-fixed; 11 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-INCLUDE/delimiter=; 12 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-INCLUDE/operation=replace 13 | environment/project/com.android.toolchain.gcc.2076706790/GCC-H-INCLUDE/value=${GCC-H-1};${GCC-H-2}; 14 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-1/delimiter=; 15 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-1/operation=replace 16 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-1/value=${ANDROID_NDK_HOME}/platforms/android-14/arch-arm/usr/include; 17 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-2/delimiter=; 18 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-2/operation=replace 19 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-2/value=${ANDROID_NDK_HOME}/platforms/android-14/arch-arm/usr/include/android; 20 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-INCLUDE/delimiter=; 21 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-INCLUDE/operation=replace 22 | environment/project/com.android.toolchain.gcc.2076706790/JNI-H-INCLUDE/value=${JNI-H-1};${JNI-H-2}; 23 | environment/project/com.android.toolchain.gcc.2076706790/append=true 24 | environment/project/com.android.toolchain.gcc.2076706790/appendContributed=true 25 | -------------------------------------------------------------------------------- /FileObserver/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FileObserver/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := fileobserver_jni 6 | LOCAL_SRC_FILES := fileobserver_jni.cpp 7 | LOCAL_LDLIBS := -lc -lm -lstdc++ -ldl -llog 8 | include $(BUILD_SHARED_LIBRARY) 9 | -------------------------------------------------------------------------------- /FileObserver/jni/Logger.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_FACE_LOG_H_____ 2 | #define _AM_FACE_LOG_H_____ 3 | 4 | #include 5 | 6 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) 7 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) 8 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) 9 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) 10 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) 11 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__) 12 | #endif//_AM_FACE_LOG_H_____ 13 | -------------------------------------------------------------------------------- /FileObserver/jni/fileobserver_jni.cpp: -------------------------------------------------------------------------------- 1 | /* //device/libs/android_runtime/android_util_FileObserver.cpp 2 | ** 3 | ** Copyright 2006, The Android Open Source Project 4 | ** 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); 6 | ** you may not use this file except in compliance with the License. 7 | ** You may obtain a copy of the License at 8 | ** 9 | ** http://www.apache.org/licenses/LICENSE-2.0 10 | ** 11 | ** Unless required by applicable law or agreed to in writing, software 12 | ** distributed under the License is distributed on an "AS IS" BASIS, 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ** See the License for the specific language governing permissions and 15 | ** limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "logger.h" 27 | 28 | #define HAVE_INOTIFY 29 | #ifdef HAVE_INOTIFY 30 | #include 31 | #endif 32 | 33 | #define LOG_TAG "libfileobserver" 34 | 35 | static jmethodID method_onEvent; 36 | 37 | static jint Native_init(JNIEnv* env, jobject object) 38 | { 39 | #ifdef HAVE_INOTIFY 40 | 41 | return (jint)inotify_init(); 42 | 43 | #else // HAVE_INOTIFY 44 | return -1; 45 | 46 | #endif // HAVE_INOTIFY 47 | } 48 | 49 | static void Native_observe(JNIEnv* env, jobject object, jint fd) 50 | { 51 | #ifdef HAVE_INOTIFY 52 | 53 | char event_buf[512]; 54 | struct inotify_event* event; 55 | 56 | while (1) 57 | { 58 | int event_pos = 0; 59 | int num_bytes = read(fd, event_buf, sizeof(event_buf)); 60 | 61 | if (num_bytes < (int)sizeof(*event)) 62 | { 63 | if (errno == EINTR) 64 | continue; 65 | 66 | LOGE("***** ERROR! Native_observe() got a short event!"); 67 | return; 68 | } 69 | 70 | while (num_bytes >= (int)sizeof(*event)) 71 | { 72 | int event_size; 73 | event = (struct inotify_event *)(event_buf + event_pos); 74 | 75 | jstring path = NULL; 76 | 77 | if (event->len > 0) 78 | { 79 | path = env->NewStringUTF(event->name); 80 | } 81 | 82 | env->CallVoidMethod(object, method_onEvent, event->wd, event->mask,event->cookie,path); 83 | if (env->ExceptionCheck()) 84 | { 85 | env->ExceptionDescribe(); 86 | env->ExceptionClear(); 87 | } 88 | if (path != NULL) 89 | { 90 | env->DeleteLocalRef(path); 91 | } 92 | 93 | event_size = sizeof(*event) + event->len; 94 | num_bytes -= event_size; 95 | event_pos += event_size; 96 | } 97 | } 98 | 99 | #endif // HAVE_INOTIFY 100 | } 101 | 102 | static jint Native_startWatching(JNIEnv* env, jobject object, jint fd, 103 | jstring pathString, jint mask) 104 | { 105 | int res = -1; 106 | 107 | #ifdef HAVE_INOTIFY 108 | 109 | if (fd >= 0) 110 | { 111 | const char* path = env->GetStringUTFChars(pathString, NULL); 112 | 113 | res = inotify_add_watch(fd, path, mask); 114 | 115 | //LOGD("inotify_add_watch res %d,errno:%d",res,errno); 116 | env->ReleaseStringUTFChars(pathString, path); 117 | } 118 | 119 | #endif // HAVE_INOTIFY 120 | return res; 121 | } 122 | 123 | static void Native_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd) 124 | { 125 | #ifdef HAVE_INOTIFY 126 | 127 | inotify_rm_watch((int)fd, (uint32_t)wfd); 128 | 129 | #endif // HAVE_INOTIFY 130 | } 131 | 132 | static JNINativeMethod sMethods[] = 133 | { 134 | /* name, signature, funcPtr */ 135 | { "init", "()I", (void*) Native_init }, 136 | { "observe", "(I)V", (void*) Native_observe }, 137 | { "startWatching", "(ILjava/lang/String;I)I", (void*) Native_startWatching }, 138 | { "stopWatching", "(II)V", (void*) Native_stopWatching } 139 | 140 | }; 141 | 142 | 143 | int register_os_android_FileWatcher(JNIEnv* env) 144 | { 145 | jclass clazz; 146 | 147 | clazz = env->FindClass("custom/fileobserver/FileObserver$ObserverThread"); 148 | 149 | if (clazz == NULL) 150 | { 151 | LOGE("Can't find custom.fileobserver.FileObserver$ObserverThread"); 152 | return -1; 153 | } 154 | 155 | method_onEvent = env->GetMethodID(clazz, "onEvent","(IIILjava/lang/String;)V"); 156 | if (method_onEvent == NULL) 157 | { 158 | LOGE("Can't find FileObserver.onEvent(int, int, String)"); 159 | return -1; 160 | } 161 | 162 | int res = env->RegisterNatives(clazz, sMethods, 163 | (sizeof(sMethods) / sizeof(sMethods[0]))); 164 | return res; 165 | } 166 | 167 | jint JNI_OnLoad(JavaVM* vm, void* reserved) 168 | { 169 | JNIEnv* env = NULL; 170 | jint result = -1; 171 | jclass native = NULL; 172 | 173 | if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 174 | { 175 | LOGD("ERROR: GetEnv failed1\n"); 176 | return -1; 177 | } 178 | 179 | result = register_os_android_FileWatcher(env); 180 | if (result < 0) 181 | { 182 | LOGD("ERROR: register_FileWatcher failed1\n"); 183 | return -1; 184 | } 185 | 186 | return JNI_VERSION_1_4; 187 | } 188 | -------------------------------------------------------------------------------- /FileObserver/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/libs/android-support-v4.jar -------------------------------------------------------------------------------- /FileObserver/libs/armeabi/libFileObserver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/libs/armeabi/libFileObserver.so -------------------------------------------------------------------------------- /FileObserver/libs/armeabi/libfileobserver_jni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/libs/armeabi/libfileobserver_jni.so -------------------------------------------------------------------------------- /FileObserver/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /FileObserver/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /FileObserver/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=false 16 | -------------------------------------------------------------------------------- /FileObserver/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /FileObserver/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /FileObserver/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doyee/FileObserver-android/c3d51e84eee9412c44634e37e75ca55509f429b1/FileObserver/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /FileObserver/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /FileObserver/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /FileObserver/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FileWather 4 | 5 | -------------------------------------------------------------------------------- /FileObserver/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /FileObserver/src/custom/fileobserver/FileListener.java: -------------------------------------------------------------------------------- 1 | package custom.fileobserver; 2 | 3 | public interface FileListener { 4 | public void onFileCreated(String name); 5 | public void onFileDeleted(String name); 6 | public void onFileModified(String name); 7 | public void onFileRenamed(String oldName, String newName); 8 | } 9 | -------------------------------------------------------------------------------- /FileObserver/src/custom/fileobserver/FileObserver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package custom.fileobserver; 18 | import android.os.Handler; 19 | import android.os.HandlerThread; 20 | import android.os.Process; 21 | import android.util.Log; 22 | 23 | 24 | import java.io.File; 25 | import java.io.FileFilter; 26 | import java.lang.ref.WeakReference; 27 | import java.util.HashMap; 28 | import java.util.Iterator; 29 | 30 | /** 31 | * Monitors files (using inotify) 32 | * to fire an event after files are accessed or changed by by any process on 33 | * the device (including this one). FileObserver is an abstract class; 34 | * subclasses must implement the event handler {@link #onEvent(int, String)}. 35 | * 36 | *

Each FileObserver instance monitors a single file or directory. 37 | * If a directory is monitored, events will be triggered for all files and 38 | * subdirectories (recursively) inside the monitored directory.

39 | * 40 | *

An event mask is used to specify which changes or actions to report. 41 | * Event type constants are used to describe the possible changes in the 42 | * event mask as well as what actually happened in event callbacks.

43 | * 44 | *

Warning: If a FileObserver is garbage collected, it 45 | * will stop sending events. To ensure you keep receiving events, you must 46 | * keep a reference to the FileObserver instance from some other live object.

47 | */ 48 | /** 49 | * 50 | * @author Dai Dongsheng 51 | * Email: doyee@163.com 52 | * 53 | */ 54 | public abstract class FileObserver { 55 | /** Event type: Data was read from a file */ 56 | public static final int ACCESS = 0x00000001; 57 | /** Event type: Data was written to a file */ 58 | public static final int MODIFY = 0x00000002; 59 | /** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */ 60 | public static final int ATTRIB = 0x00000004; 61 | /** Event type: Someone had a file or directory open for writing, and closed it */ 62 | public static final int CLOSE_WRITE = 0x00000008; 63 | /** Event type: Someone had a file or directory open read-only, and closed it */ 64 | public static final int CLOSE_NOWRITE = 0x00000010; 65 | /** Event type: A file or directory was opened */ 66 | public static final int OPEN = 0x00000020; 67 | /** Event type: A file or subdirectory was moved from the monitored directory */ 68 | public static final int MOVED_FROM = 0x00000040; 69 | /** Event type: A file or subdirectory was moved to the monitored directory */ 70 | public static final int MOVED_TO = 0x00000080; 71 | /** Event type: A new file or subdirectory was created under the monitored directory */ 72 | public static final int CREATE = 0x00000100; 73 | /** Event type: A file was deleted from the monitored directory */ 74 | public static final int DELETE = 0x00000200; 75 | /** Event type: The monitored file or directory was deleted; monitoring effectively stops */ 76 | public static final int DELETE_SELF = 0x00000400; 77 | /** Event type: The monitored file or directory was moved; monitoring continues */ 78 | public static final int MOVE_SELF = 0x00000800; 79 | 80 | 81 | public static final int UNMOUNT = 0x00002000; 82 | public static final int Q_OVERFLOW = 0x00004000; 83 | public static final int IGNORED = 0x00008000; 84 | 85 | public static final int CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE); 86 | public static final int MOVE = (MOVED_FROM | MOVED_TO); 87 | 88 | public static final int ONLYDIR = 0x01000000; 89 | public static final int DONT_FOLLOW = 0x02000000; 90 | public static final int MASK_ADD = 0x20000000; 91 | public static final int ISDIR = 0x40000000 ; 92 | public static final int ONESHOT = 0x80000000; 93 | 94 | /** Event mask: All valid event types, combined */ 95 | public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE 96 | | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE 97 | | DELETE_SELF | MOVE_SELF; 98 | public static int FILE_CHANGED = CREATE | DELETE | MOVED_FROM | MOVED_TO | CLOSE_WRITE;/* MODIFY | ATTRIB*/; 99 | 100 | private static final String LOG_TAG = "FileObserver"; 101 | 102 | 103 | private static class FolderFilter implements FileFilter{ 104 | public boolean accept(File pathname) { 105 | return pathname.isDirectory(); 106 | } 107 | } 108 | private static class ObserverThread extends Thread { 109 | private HashMap> mObservers = new HashMap>(); 110 | private HashMap mListPath = new HashMap(); 111 | private FolderFilter mFilter = new FolderFilter(); 112 | private int m_fd; 113 | 114 | public ObserverThread() { 115 | super("FileObserver"); 116 | m_fd = init(); 117 | } 118 | 119 | public void run() { 120 | observe(m_fd); 121 | } 122 | 123 | public int startWatching(String observed, String path, int mask, FileObserver observer) { 124 | int wfd = startWatching(m_fd, path, mask); 125 | 126 | Integer i = new Integer(wfd); 127 | if (wfd <= 0) { 128 | 129 | return i; 130 | } 131 | 132 | synchronized (mObservers) { 133 | mObservers.put(i, new WeakReference(observer)); 134 | mListPath.put(i, path.replaceFirst(observed, "")); 135 | 136 | if(observer.mWatchSubDir){ 137 | File rootFolder = new File(path); 138 | File[] childFolders = rootFolder.listFiles(mFilter); 139 | if((childFolders != null)) 140 | { 141 | for(int index = 0; index < childFolders.length; index++) 142 | startWatching(observed, childFolders[index].getPath(), mask, observer); 143 | } 144 | } 145 | } 146 | 147 | return i; 148 | } 149 | 150 | public void stopWatching(int descriptor, FileObserver observer) { 151 | synchronized(mObservers) 152 | { 153 | stopWatching(m_fd, descriptor); 154 | mListPath.remove(descriptor); 155 | mObservers.remove(descriptor); 156 | 157 | Iterator it = mListPath.keySet().iterator(); 158 | while(it.hasNext()) 159 | { 160 | Integer fd = it.next(); 161 | if(mObservers.get(fd).get() == observer) 162 | { 163 | stopWatching(m_fd, fd); 164 | it.remove(); 165 | mObservers.remove(fd); 166 | } 167 | } 168 | } 169 | } 170 | 171 | public void onEvent(int wfd, int mask, int cookie, String path) { 172 | // look up our observer, fixing up the map if necessary... 173 | FileObserver observer = null; 174 | 175 | synchronized (mObservers) { 176 | WeakReference weak = mObservers.get(wfd); 177 | if (weak != null) { // can happen with lots of events from a 178 | // dead wfd 179 | observer = (FileObserver) weak.get(); 180 | if (observer == null) { 181 | mObservers.remove(wfd); 182 | mListPath.remove(wfd); 183 | } 184 | } 185 | } 186 | 187 | // ...then call out to the observer without the sync lock held 188 | if (observer == null) { 189 | Log.i(LOG_TAG,"onEvent observer null ,return..."); 190 | return; 191 | } 192 | 193 | try { 194 | String observed = observer.mPath ; 195 | String newAbsPath = observed + mListPath.get(wfd); 196 | if (path != null) { 197 | if (newAbsPath.length() > 0) { 198 | newAbsPath += "/"; 199 | } 200 | newAbsPath += path; 201 | } 202 | 203 | if ((mask & (CREATE | ISDIR)) != 0) { 204 | //auto to watch new created subdirectory 205 | if(observer.mWatchSubDir){ 206 | startWatching(observed, newAbsPath, observer.mMask, observer); 207 | } 208 | 209 | } 210 | 211 | observer.onEvent(mask, cookie,newAbsPath); 212 | } catch (Throwable throwable) { 213 | Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " 214 | + observer, throwable); 215 | } 216 | 217 | } 218 | 219 | private native int init(); 220 | private native void observe(int fd); 221 | private native int startWatching(int fd, String path, int mask); 222 | private native void stopWatching(int fd, int wfd); 223 | } 224 | 225 | 226 | private static ObserverThread s_observerThread; 227 | 228 | static { 229 | try{ 230 | System.loadLibrary("fileobserver_jni"); 231 | }catch (UnsatisfiedLinkError e) { 232 | e.printStackTrace(); 233 | } 234 | 235 | 236 | /*try { 237 | Thread.sleep(5000); 238 | } catch (InterruptedException e) { 239 | e.printStackTrace(); 240 | }*/ 241 | 242 | s_observerThread = new ObserverThread(); 243 | s_observerThread.start(); 244 | } 245 | 246 | // instance 247 | private String mPath; 248 | private Integer mDescriptor; 249 | private int mMask; 250 | private boolean mWatchSubDir; 251 | 252 | String mThreadName = FileObserver.class.getSimpleName(); 253 | HandlerThread mThread; 254 | Handler mThreadHandler; 255 | 256 | /** 257 | * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). 258 | */ 259 | public FileObserver(String path) { 260 | this(path, ALL_EVENTS); 261 | 262 | } 263 | 264 | public FileObserver(String path, int mask) { 265 | this(path,false,mask); 266 | } 267 | /** 268 | * Create a new file observer for a certain file or directory. 269 | * Monitoring does not start on creation! You must call 270 | * {@link #startWatching()} before you will receive events. 271 | * 272 | * @param path The file or directory to monitor 273 | * @param watchSubDir If the sub directory need monitor ,set to true,default false 274 | * @param mask The event or events (added together) to watch for 275 | */ 276 | public FileObserver(String path, boolean watchSubDir,int mask) { 277 | mPath = path; 278 | mMask = mask; 279 | mDescriptor = -1; 280 | mWatchSubDir = watchSubDir; 281 | } 282 | 283 | protected void finalize() { 284 | stopWatching(); 285 | } 286 | 287 | /** 288 | * Start watching for events. The monitored file or directory must exist at 289 | * this time, or else no events will be reported (even if it appears later). 290 | * If monitoring is already started, this call has no effect. 291 | */ 292 | public void startWatching() { 293 | mThreadName = FileWatcher.class.getSimpleName(); 294 | if (mThread == null || !mThread.isAlive()) { 295 | Log.i(LOG_TAG,"startFileWather new HandlerThread..."); 296 | mThread = new HandlerThread(mThreadName,Process.THREAD_PRIORITY_BACKGROUND); 297 | mThread.setDaemon(true); 298 | mThread.start(); 299 | 300 | mThreadHandler = new Handler(mThread.getLooper()); 301 | mThreadHandler.post(new Runnable() { 302 | @Override 303 | public void run() { 304 | Log.i(LOG_TAG,"startWatching mDescriptor:" + mDescriptor); 305 | if (mDescriptor < 0) { 306 | mDescriptor = s_observerThread.startWatching(mPath, mPath, mMask, FileObserver.this); 307 | Log.i(LOG_TAG,"startWatching finished mDescriptor: " + mDescriptor); 308 | } 309 | } 310 | }); 311 | } 312 | 313 | } 314 | 315 | /** 316 | * Stop watching for events. Some events may be in process, so events 317 | * may continue to be reported even after this method completes. If 318 | * monitoring is already stopped, this call has no effect. 319 | */ 320 | public void stopWatching() { 321 | if(null != mThreadHandler && null != mThread && mThread.isAlive()){ 322 | mThreadHandler.post(new Runnable() { 323 | @Override 324 | public void run() { 325 | Log.i(LOG_TAG,"stopWatching mDescriptor:" + mDescriptor); 326 | if (mDescriptor < 0) { 327 | Log.i(LOG_TAG,"stopWatching already stopped:" + mDescriptor); 328 | return; 329 | } 330 | s_observerThread.stopWatching(mDescriptor, FileObserver.this); 331 | mDescriptor = -1; 332 | Log.i(LOG_TAG,"stopWatching finished:" + mDescriptor); 333 | 334 | mThreadHandler = null; 335 | mThread.quit(); 336 | mThread = null; 337 | } 338 | }); 339 | } 340 | } 341 | 342 | /** 343 | * The event handler, which must be implemented by subclasses. 344 | * 345 | *

This method is invoked on a special FileObserver thread. 346 | * It runs independently of any threads, so take care to use appropriate 347 | * synchronization! Consider using {@link Handler#post(Runnable)} to shift 348 | * event handling work to the main thread to avoid concurrency problems.

349 | * 350 | *

Event handlers must not throw exceptions.

351 | * 352 | * @param event The type of event which happened 353 | * @param path The path, relative to the main monitored file or directory, 354 | * of the file or directory which triggered the event 355 | */ 356 | public abstract void onEvent(int event, int cookie,String path); 357 | 358 | } 359 | -------------------------------------------------------------------------------- /FileObserver/src/custom/fileobserver/FileWatcher.java: -------------------------------------------------------------------------------- 1 | 2 | package custom.fileobserver; 3 | 4 | import java.util.Hashtable; 5 | 6 | import android.util.Log; 7 | /** 8 | * 9 | * @author Dai Dongsheng 10 | * Email: doyee@163.com 11 | * FileWatcher support subdirectory(recursively) 12 | */ 13 | public class FileWatcher extends FileObserver { 14 | FileListener mFileListener; 15 | Hashtable mRenameCookies = new Hashtable(); 16 | public FileWatcher(String path) { 17 | this(path, ALL_EVENTS); 18 | } 19 | 20 | public FileWatcher(String path, int mask) { 21 | this(path, false,mask); 22 | } 23 | 24 | public FileWatcher(String path, boolean watchsubdir,int mask) { 25 | super(path, watchsubdir,mask); 26 | } 27 | 28 | public void setFileListener(FileListener fl){ 29 | mFileListener = fl; 30 | } 31 | 32 | @Override 33 | public void onEvent(int event,int cookie,String path) { 34 | switch (event) { 35 | case ACCESS: 36 | Log.i("FileWatcher", "ACCESS: " + path); 37 | break; 38 | case ATTRIB: 39 | Log.i("FileWatcher", "ATTRIB: " + path); 40 | if(null != mFileListener){ 41 | mFileListener.onFileModified(path); 42 | } 43 | break; 44 | case CLOSE_NOWRITE: 45 | Log.i("FileWatcher", "CLOSE_NOWRITE: " + path); 46 | break; 47 | case CLOSE_WRITE: 48 | Log.i("FileWatcher", "CLOSE_WRITE: " + path); 49 | if(null != mFileListener){ 50 | mFileListener.onFileModified(path); 51 | } 52 | break; 53 | case CREATE: 54 | Log.i("FileWatcher", "CREATE: " + path); 55 | if(null != mFileListener){ 56 | mFileListener.onFileCreated(path); 57 | } 58 | break; 59 | case DELETE: 60 | Log.i("FileWatcher", "DELETE: " + path); 61 | if(null != mFileListener){ 62 | mFileListener.onFileDeleted(path); 63 | } 64 | break; 65 | case DELETE_SELF: 66 | Log.i("FileWatcher", "DELETE_SELF: " + path); 67 | if(null != mFileListener){ 68 | mFileListener.onFileDeleted(path); 69 | } 70 | break; 71 | case MODIFY: 72 | Log.i("FileWatcher", "MODIFY: " + path); 73 | if(null != mFileListener){ 74 | mFileListener.onFileModified(path); 75 | } 76 | break; 77 | case MOVE_SELF: 78 | Log.i("FileWatcher", "MOVE_SELF: " + path); 79 | break; 80 | case MOVED_FROM: 81 | Log.i("FileWatcher", "MOVED_FROM: " + path); 82 | mRenameCookies.put(cookie, path); 83 | break; 84 | case MOVED_TO: 85 | Log.i("FileWatcher", "MOVED_TO: " + path); 86 | if(null != mFileListener){ 87 | String oldName = mRenameCookies.remove(cookie); 88 | mFileListener.onFileRenamed(oldName, path); 89 | } 90 | break; 91 | case OPEN: 92 | Log.i("FileWatcher", "OPEN: " + path); 93 | break; 94 | default: 95 | Log.i("FileWatcher", "DEFAULT(" + event + ") : " + path); 96 | switch(event - ISDIR){ 97 | case ACCESS: 98 | Log.i("FileWatcher", "ACCESS: " + path); 99 | break; 100 | case ATTRIB: 101 | Log.i("FileWatcher", "ATTRIB: " + path); 102 | if(null != mFileListener){ 103 | mFileListener.onFileModified(path); 104 | } 105 | break; 106 | case CLOSE_NOWRITE: 107 | Log.i("FileWatcher", "CLOSE_NOWRITE: " + path); 108 | break; 109 | case CLOSE_WRITE: 110 | Log.i("FileWatcher", "CLOSE_WRITE: " + path); 111 | if(null != mFileListener){ 112 | mFileListener.onFileModified(path); 113 | } 114 | break; 115 | case CREATE: 116 | Log.i("FileWatcher", "CREATE: " + path); 117 | if(null != mFileListener){ 118 | mFileListener.onFileCreated(path); 119 | } 120 | break; 121 | case DELETE: 122 | Log.i("FileWatcher", "DELETE: " + path); 123 | if(null != mFileListener){ 124 | mFileListener.onFileDeleted(path); 125 | } 126 | break; 127 | case DELETE_SELF: 128 | Log.i("FileWatcher", "DELETE_SELF: " + path); 129 | if(null != mFileListener){ 130 | mFileListener.onFileDeleted(path); 131 | } 132 | break; 133 | case MODIFY: 134 | Log.i("FileWatcher", "MODIFY: " + path); 135 | if(null != mFileListener){ 136 | mFileListener.onFileModified(path); 137 | } 138 | break; 139 | case MOVE_SELF: 140 | Log.i("FileWatcher", "MOVE_SELF: " + path); 141 | break; 142 | case MOVED_FROM: 143 | Log.i("FileWatcher", "MOVED_FROM: " + path); 144 | mRenameCookies.put(cookie, path); 145 | break; 146 | case MOVED_TO: 147 | Log.i("FileWatcher", "MOVED_TO: " + path); 148 | if(null != mFileListener){ 149 | String oldName = mRenameCookies.remove(cookie); 150 | mFileListener.onFileRenamed(oldName, path); 151 | } 152 | break; 153 | case OPEN: 154 | Log.i("FileWatcher", "OPEN: " + path); 155 | break; 156 | } 157 | break; 158 | } 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /FileObserver/src/custom/fileobserver/TestActivity.java: -------------------------------------------------------------------------------- 1 | package custom.fileobserver; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.Environment; 6 | import android.util.Log; 7 | 8 | public class TestActivity extends Activity { 9 | 10 | FileWatcher mWatcher; 11 | final String TAG = TestActivity.class.getSimpleName(); 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | String sdcard = Environment.getExternalStorageDirectory().getAbsolutePath(); 17 | String dcim = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(); 18 | //mWatcher = new FileWatcher(sdcard,true,FileWatcher.FILE_CHANGED); 19 | mWatcher = new FileWatcher(dcim,true,FileWatcher.FILE_CHANGED); 20 | mWatcher.setFileListener(mFileListener); 21 | mWatcher.startWatching(); 22 | } 23 | 24 | FileListener mFileListener = new FileListener(){ 25 | 26 | @Override 27 | public void onFileCreated(String name) { 28 | Log.i(TAG, "onFileCreated " + name); 29 | } 30 | 31 | @Override 32 | public void onFileDeleted(String name) { 33 | Log.i(TAG, "onFileDeleted " + name); 34 | } 35 | 36 | @Override 37 | public void onFileModified(String name) { 38 | Log.i(TAG, "onFileModified " + name); 39 | } 40 | 41 | @Override 42 | public void onFileRenamed(String oldName, String newName) { 43 | Log.i(TAG, "onFileRenamed from: " + oldName + " to: " + newName); 44 | } 45 | 46 | }; 47 | @Override 48 | protected void onResume() { 49 | 50 | super.onResume(); 51 | } 52 | 53 | @Override 54 | protected void onPause() { 55 | 56 | super.onPause(); 57 | } 58 | 59 | @Override 60 | protected void onUserLeaveHint() { 61 | 62 | super.onUserLeaveHint(); 63 | } 64 | 65 | @Override 66 | protected void onStop() { 67 | super.onStop(); 68 | } 69 | 70 | @Override 71 | protected void onDestroy() { 72 | mWatcher.stopWatching(); 73 | super.onDestroy(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /FileWatcher.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.Iterator; 6 | import java.util.Stack; 7 | import android.os.FileObserver; 8 | import android.os.Handler; 9 | import android.os.HandlerThread; 10 | import android.os.Process; 11 | import android.util.Log; 12 | /** 13 | * 14 | * @author ddai 15 | * FileWatcher support subdirectory(recursively) 16 | */ 17 | public class FileWatcher extends FileObserver { 18 | /** Only modification events */ 19 | public static int CHANGES_ONLY = CREATE | DELETE | 20 | CLOSE_WRITE | MOVED_FROM | MOVED_TO; 21 | 22 | ArrayList mObservers; 23 | String mPath; 24 | int mMask; 25 | 26 | String mThreadName = FileWatcher.class.getSimpleName(); 27 | HandlerThread mThread; 28 | Handler mThreadHandler; 29 | 30 | public FileWatcher(String path) { 31 | this(path, ALL_EVENTS); 32 | } 33 | 34 | public FileWatcher(String path, int mask) { 35 | super(path, mask); 36 | mPath = path; 37 | mMask = mask; 38 | } 39 | 40 | @Override 41 | public void startWatching() { 42 | mThreadName = FileWatcher.class.getSimpleName(); 43 | if (mThread == null || !mThread.isAlive()) { 44 | 45 | FileLogger.printInfo("startFileWather new HandlerThread..."); 46 | mThread = new HandlerThread(mThreadName,Process.THREAD_PRIORITY_BACKGROUND); 47 | mThread.start(); 48 | 49 | mThreadHandler = new Handler(mThread.getLooper()); 50 | mThreadHandler.post(new startRunnable()); 51 | } 52 | } 53 | 54 | @Override 55 | public void stopWatching() { 56 | if(null != mThreadHandler && null != mThread && mThread.isAlive()){ 57 | mThreadHandler.post(new stopRunnable()); 58 | } 59 | mThreadHandler = null; 60 | mThread.quit(); 61 | mThread = null; 62 | } 63 | 64 | @Override 65 | public void onEvent(int event, String path) { 66 | switch (event) { 67 | case FileObserver.ACCESS: 68 | Log.i("FileWatcher", "ACCESS: " + path); 69 | break; 70 | case FileObserver.ATTRIB: 71 | Log.i("FileWatcher", "ATTRIB: " + path); 72 | break; 73 | case FileObserver.CLOSE_NOWRITE: 74 | Log.i("FileWatcher", "CLOSE_NOWRITE: " + path); 75 | break; 76 | case FileObserver.CLOSE_WRITE: 77 | Log.i("FileWatcher", "CLOSE_WRITE: " + path); 78 | break; 79 | case FileObserver.CREATE: 80 | Log.i("FileWatcher", "CREATE: " + path); 81 | break; 82 | case FileObserver.DELETE: 83 | Log.i("FileWatcher", "DELETE: " + path); 84 | break; 85 | case FileObserver.DELETE_SELF: 86 | Log.i("FileWatcher", "DELETE_SELF: " + path); 87 | break; 88 | case FileObserver.MODIFY: 89 | Log.i("FileWatcher", "MODIFY: " + path); 90 | break; 91 | case FileObserver.MOVE_SELF: 92 | Log.i("FileWatcher", "MOVE_SELF: " + path); 93 | break; 94 | case FileObserver.MOVED_FROM: 95 | Log.i("FileWatcher", "MOVED_FROM: " + path); 96 | break; 97 | case FileObserver.MOVED_TO: 98 | Log.i("FileWatcher", "MOVED_TO: " + path); 99 | break; 100 | case FileObserver.OPEN: 101 | Log.i("FileWatcher", "OPEN: " + path); 102 | break; 103 | default: 104 | Log.i("FileWatcher", "DEFAULT(" + event + ";) : " + path); 105 | break; 106 | } 107 | } 108 | 109 | /** 110 | * Monitor single directory and dispatch all events to its parent, with full 111 | * path. 112 | */ 113 | class SingleFileObserver extends FileObserver { 114 | String mPath; 115 | 116 | public SingleFileObserver(String path) { 117 | this(path, ALL_EVENTS); 118 | mPath = path; 119 | } 120 | 121 | public SingleFileObserver(String path, int mask) { 122 | super(path, mask); 123 | mPath = path; 124 | } 125 | 126 | @Override 127 | public void onEvent(int event, String path) { 128 | String newPath = mPath + "/" + path; 129 | FileWatcher.this.onEvent(event, newPath); 130 | } 131 | } 132 | 133 | class startRunnable implements Runnable { 134 | @Override 135 | public void run() { 136 | synchronized (FileWatcher.this) { 137 | if (mObservers != null) 138 | return; 139 | 140 | mObservers = new ArrayList(); 141 | Stack stack = new Stack(); 142 | stack.push(mPath); 143 | 144 | while (!stack.isEmpty()) { 145 | String parent = String.valueOf(stack.pop()); 146 | mObservers.add(new SingleFileObserver(parent, mMask)); 147 | File path = new File(parent); 148 | File[] files = path.listFiles(); 149 | if (null == files) 150 | continue; 151 | for (File f : files) { 152 | if (f.isDirectory() && !f.getName().equals(".") 153 | && !f.getName().equals("..")) { 154 | stack.push(f.getPath()); 155 | } 156 | } 157 | } 158 | 159 | Iterator it = mObservers.iterator(); 160 | while (it.hasNext()) { 161 | FileObserver sfo = it.next(); 162 | sfo.startWatching(); 163 | } 164 | } 165 | } 166 | } 167 | 168 | class stopRunnable implements Runnable { 169 | @Override 170 | public void run() { 171 | synchronized (FileWatcher.this) { 172 | if (mObservers == null) 173 | return; 174 | 175 | Iterator it = mObservers.iterator(); 176 | while (it.hasNext()) { 177 | FileObserver sfo = it.next(); 178 | sfo.stopWatching(); 179 | } 180 | mObservers.clear(); 181 | mObservers = null; 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FileObserver-android 2 | ==================== 3 | 4 | custom FileObserver that support events will be triggered for all files and * subdirectories (recursively) inside the monitored directory. --------------------------------------------------------------------------------