The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 android.os; |
| 18 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 19 | import android.annotation.NonNull; |
Jeff Sharkey | 902316d | 2016-08-23 10:24:56 -0600 | [diff] [blame] | 20 | import android.annotation.Nullable; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 21 | import android.util.Log; |
| 22 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 23 | import java.io.File; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 24 | import java.lang.ref.WeakReference; |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 25 | import java.util.Arrays; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 26 | import java.util.HashMap; |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 27 | import java.util.List; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 28 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 29 | /** |
| 30 | * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>) |
| 31 | * to fire an event after files are accessed or changed by by any process on |
| 32 | * the device (including this one). FileObserver is an abstract class; |
| 33 | * subclasses must implement the event handler {@link #onEvent(int, String)}. |
| 34 | * |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 35 | * <p>Each FileObserver instance can monitor multiple files or directories. |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 36 | * If a directory is monitored, events will be triggered for all files and |
Scott Main | 6aad995 | 2013-01-07 18:51:49 -0800 | [diff] [blame] | 37 | * subdirectories inside the monitored directory.</p> |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 38 | * |
| 39 | * <p>An event mask is used to specify which changes or actions to report. |
| 40 | * Event type constants are used to describe the possible changes in the |
| 41 | * event mask as well as what actually happened in event callbacks.</p> |
| 42 | * |
| 43 | * <p class="caution"><b>Warning</b>: If a FileObserver is garbage collected, it |
| 44 | * will stop sending events. To ensure you keep receiving events, you must |
| 45 | * keep a reference to the FileObserver instance from some other live object.</p> |
| 46 | */ |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 47 | public abstract class FileObserver { |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 48 | /** Event type: Data was read from a file */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 49 | public static final int ACCESS = 0x00000001; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 50 | /** Event type: Data was written to a file */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 51 | public static final int MODIFY = 0x00000002; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 52 | /** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 53 | public static final int ATTRIB = 0x00000004; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 54 | /** Event type: Someone had a file or directory open for writing, and closed it */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 55 | public static final int CLOSE_WRITE = 0x00000008; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 56 | /** Event type: Someone had a file or directory open read-only, and closed it */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 57 | public static final int CLOSE_NOWRITE = 0x00000010; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 58 | /** Event type: A file or directory was opened */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 59 | public static final int OPEN = 0x00000020; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 60 | /** Event type: A file or subdirectory was moved from the monitored directory */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 61 | public static final int MOVED_FROM = 0x00000040; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 62 | /** Event type: A file or subdirectory was moved to the monitored directory */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 63 | public static final int MOVED_TO = 0x00000080; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 64 | /** Event type: A new file or subdirectory was created under the monitored directory */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 65 | public static final int CREATE = 0x00000100; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 66 | /** Event type: A file was deleted from the monitored directory */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 67 | public static final int DELETE = 0x00000200; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 68 | /** Event type: The monitored file or directory was deleted; monitoring effectively stops */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 69 | public static final int DELETE_SELF = 0x00000400; |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 70 | /** Event type: The monitored file or directory was moved; monitoring continues */ |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 71 | public static final int MOVE_SELF = 0x00000800; |
| 72 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 73 | /** Event mask: All valid event types, combined */ |
| 74 | public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 75 | | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 76 | | DELETE_SELF | MOVE_SELF; |
Joe Onorato | 9bb8fd7 | 2009-07-28 18:24:51 -0700 | [diff] [blame] | 77 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 78 | private static final String LOG_TAG = "FileObserver"; |
| 79 | |
| 80 | private static class ObserverThread extends Thread { |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 81 | private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>(); |
| 82 | private int m_fd; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 83 | |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 84 | public ObserverThread() { |
| 85 | super("FileObserver"); |
| 86 | m_fd = init(); |
| 87 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 88 | |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 89 | public void run() { |
| 90 | observe(m_fd); |
| 91 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 92 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 93 | public int[] startWatching(List<File> files, int mask, FileObserver observer) { |
| 94 | final int count = files.size(); |
| 95 | final String[] paths = new String[count]; |
| 96 | for (int i = 0; i < count; ++i) { |
| 97 | paths[i] = files.get(i).getAbsolutePath(); |
| 98 | } |
| 99 | final int[] wfds = new int[count]; |
| 100 | Arrays.fill(wfds, -1); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 101 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 102 | startWatching(m_fd, paths, mask, wfds); |
| 103 | |
| 104 | final WeakReference<FileObserver> fileObserverWeakReference = |
| 105 | new WeakReference<>(observer); |
| 106 | synchronized (m_observers) { |
| 107 | for (int wfd : wfds) { |
| 108 | if (wfd >= 0) { |
| 109 | m_observers.put(wfd, fileObserverWeakReference); |
| 110 | } |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 111 | } |
| 112 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 113 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 114 | return wfds; |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 115 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 116 | |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 117 | public void stopWatching(int[] descriptors) { |
| 118 | stopWatching(m_fd, descriptors); |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 119 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 120 | |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 121 | public void onEvent(int wfd, int mask, String path) { |
| 122 | // look up our observer, fixing up the map if necessary... |
| 123 | FileObserver observer = null; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 124 | |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 125 | synchronized (m_observers) { |
| 126 | WeakReference weak = m_observers.get(wfd); |
| 127 | if (weak != null) { // can happen with lots of events from a dead wfd |
| 128 | observer = (FileObserver) weak.get(); |
| 129 | if (observer == null) { |
| 130 | m_observers.remove(wfd); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | // ...then call out to the observer without the sync lock held |
| 136 | if (observer != null) { |
| 137 | try { |
| 138 | observer.onEvent(mask, path); |
| 139 | } catch (Throwable throwable) { |
| 140 | Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable); |
| 141 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 142 | } |
| 143 | } |
| 144 | |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 145 | private native int init(); |
| 146 | private native void observe(int fd); |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 147 | private native void startWatching(int fd, String[] paths, int mask, int[] wfds); |
| 148 | private native void stopWatching(int fd, int[] wfds); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | private static ObserverThread s_observerThread; |
| 152 | |
| 153 | static { |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 154 | s_observerThread = new ObserverThread(); |
| 155 | s_observerThread.start(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | // instance |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 159 | private final List<File> mFiles; |
| 160 | private int[] mDescriptors; |
| 161 | private final int mMask; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 162 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 163 | /** |
| 164 | * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 165 | * |
| 166 | * @deprecated use {@link #FileObserver(File)} instead. |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 167 | */ |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 168 | @Deprecated |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 169 | public FileObserver(String path) { |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 170 | this(new File(path)); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Equivalent to FileObserver(file, FileObserver.ALL_EVENTS). |
| 175 | */ |
| 176 | public FileObserver(@NonNull File file) { |
| 177 | this(Arrays.asList(file)); |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS). |
| 182 | * |
| 183 | * @param files The files or directories to monitor |
| 184 | */ |
| 185 | public FileObserver(@NonNull List<File> files) { |
| 186 | this(files, ALL_EVENTS); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 187 | } |
| 188 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 189 | /** |
| 190 | * Create a new file observer for a certain file or directory. |
| 191 | * Monitoring does not start on creation! You must call |
| 192 | * {@link #startWatching()} before you will receive events. |
| 193 | * |
| 194 | * @param path The file or directory to monitor |
| 195 | * @param mask The event or events (added together) to watch for |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 196 | * |
| 197 | * @deprecated use {@link #FileObserver(File, int)} instead. |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 198 | */ |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 199 | @Deprecated |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 200 | public FileObserver(String path, int mask) { |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 201 | this(new File(path), mask); |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * Create a new file observer for a certain file or directory. |
| 206 | * Monitoring does not start on creation! You must call |
| 207 | * {@link #startWatching()} before you will receive events. |
| 208 | * |
| 209 | * @param file The file or directory to monitor |
| 210 | * @param mask The event or events (added together) to watch for |
| 211 | */ |
| 212 | public FileObserver(@NonNull File file, int mask) { |
| 213 | this(Arrays.asList(file), mask); |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * Version of {@link #FileObserver(File, int)} that allows callers to monitor |
| 218 | * multiple files or directories. |
| 219 | * |
| 220 | * @param files The files or directories to monitor |
| 221 | * @param mask The event or events (added together) to watch for |
| 222 | */ |
| 223 | public FileObserver(@NonNull List<File> files, int mask) { |
| 224 | mFiles = files; |
| 225 | mMask = mask; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | protected void finalize() { |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 229 | stopWatching(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 230 | } |
| 231 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 232 | /** |
| 233 | * Start watching for events. The monitored file or directory must exist at |
| 234 | * this time, or else no events will be reported (even if it appears later). |
| 235 | * If monitoring is already started, this call has no effect. |
| 236 | */ |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 237 | public void startWatching() { |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 238 | if (mDescriptors == null) { |
| 239 | mDescriptors = s_observerThread.startWatching(mFiles, mMask, this); |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 240 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 241 | } |
| 242 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 243 | /** |
| 244 | * Stop watching for events. Some events may be in process, so events |
| 245 | * may continue to be reported even after this method completes. If |
| 246 | * monitoring is already stopped, this call has no effect. |
| 247 | */ |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 248 | public void stopWatching() { |
Sudheer Shanka | f7c79b4 | 2019-01-27 14:18:26 -0800 | [diff] [blame] | 249 | if (mDescriptors != null) { |
| 250 | s_observerThread.stopWatching(mDescriptors); |
| 251 | mDescriptors = null; |
Dan Egnor | d95244f | 2010-02-05 13:52:35 -0800 | [diff] [blame] | 252 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 253 | } |
| 254 | |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 255 | /** |
| 256 | * The event handler, which must be implemented by subclasses. |
| 257 | * |
| 258 | * <p class="note">This method is invoked on a special FileObserver thread. |
| 259 | * It runs independently of any threads, so take care to use appropriate |
| 260 | * synchronization! Consider using {@link Handler#post(Runnable)} to shift |
| 261 | * event handling work to the main thread to avoid concurrency problems.</p> |
| 262 | * |
| 263 | * <p>Event handlers must not throw exceptions.</p> |
| 264 | * |
| 265 | * @param event The type of event which happened |
| 266 | * @param path The path, relative to the main monitored file or directory, |
Jeff Sharkey | 902316d | 2016-08-23 10:24:56 -0600 | [diff] [blame] | 267 | * of the file or directory which triggered the event. This value can |
| 268 | * be {@code null} for certain events, such as {@link #MOVE_SELF}. |
Dan Egnor | 3dc1c7f | 2010-05-31 10:13:44 -0700 | [diff] [blame] | 269 | */ |
Jeff Sharkey | 902316d | 2016-08-23 10:24:56 -0600 | [diff] [blame] | 270 | public abstract void onEvent(int event, @Nullable String path); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 271 | } |