1 /* 2 * Copyright (C) 2008 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.cts; 18 19 import android.os.Environment; 20 import android.os.FileObserver; 21 import android.platform.test.annotations.AppModeFull; 22 import android.platform.test.annotations.AppModeInstant; 23 import android.test.AndroidTestCase; 24 import android.util.Pair; 25 26 import androidx.test.InstrumentationRegistry; 27 28 import java.io.File; 29 import java.io.FileOutputStream; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 34 public class FileObserverTest extends AndroidTestCase { 35 private static final String PATH = "/PATH"; 36 private static final String TEST_FILE = "file_observer_test.txt"; 37 private static final String TEST_DIR = "fileobserver_dir"; 38 private static final File EXT_STORAGE_DIR = new File(Environment.getExternalStorageDirectory(), "fileobserver_toplevel_dir"); 39 private static final int FILE_DATA = 0x20; 40 private static final int UNDEFINED = 0x8000; 41 private static final long DELAY_MSECOND = 2000; 42 helpSetUp(File dir)43 private void helpSetUp(File dir) throws Exception { 44 File testFile = new File(dir, TEST_FILE); 45 testFile.createNewFile(); 46 File testDir = new File(dir, TEST_DIR); 47 testDir.mkdirs(); 48 } 49 50 @Override setUp()51 protected void setUp() throws Exception { 52 super.setUp(); 53 File dir = getContext().getFilesDir(); 54 helpSetUp(dir); 55 56 dir = getContext().getCacheDir(); 57 helpSetUp(dir); 58 59 // Instant apps cannot access external storage 60 if (!InstrumentationRegistry.getTargetContext().getPackageManager().isInstantApp()) { 61 dir = getContext().getExternalFilesDir(null); 62 helpSetUp(dir); 63 64 dir = EXT_STORAGE_DIR; 65 dir.mkdirs(); 66 helpSetUp(dir); 67 } 68 69 // Let the setup settles 70 Thread.sleep(DELAY_MSECOND); 71 } 72 helpTearDown(File dir)73 private void helpTearDown(File dir) throws Exception { 74 File testFile = new File(dir, TEST_FILE); 75 File testDir = new File(dir, TEST_DIR); 76 File moveDestFile = new File(testDir, TEST_FILE); 77 78 if (testFile.exists()) { 79 testFile.delete(); 80 } 81 82 if (moveDestFile.exists()) { 83 moveDestFile.delete(); 84 } 85 86 if (testDir.exists()) { 87 testDir.delete(); 88 } 89 } 90 91 @Override tearDown()92 protected void tearDown() throws Exception { 93 super.tearDown(); 94 95 File dir = getContext().getFilesDir(); 96 helpTearDown(dir); 97 98 dir = getContext().getCacheDir(); 99 helpTearDown(dir); 100 101 dir = getContext().getExternalFilesDir(null); 102 helpTearDown(dir); 103 104 dir = EXT_STORAGE_DIR; 105 helpTearDown(dir); 106 if (dir.exists()) { 107 dir.delete(); 108 } 109 } 110 testConstructor()111 public void testConstructor() { 112 // new the instance 113 new MockFileObserver(new File(PATH)); 114 // new the instance 115 new MockFileObserver(new File(PATH), FileObserver.ACCESS); 116 } 117 118 /* 119 * Test point 120 * 1. Observe a dir, when it's child file have been written and closed, 121 * observer should get modify open-child modify-child and closed-write events. 122 * 2. While stop observer a dir, observer should't get any event while delete it's child file. 123 * 3. Observer a dir, when create delete a child file and delete self, 124 * observer should get create-child close-nowrite delete-child delete-self events. 125 * 4. Observer a file, the file moved from dir and the file moved to dir, move the file, 126 * file observer should get move-self event, 127 * moved from dir observer should get moved-from event, 128 * moved to dir observer should get moved-to event. 129 * 130 * On emulated storage, there may be additional operations related to case insensitivity, so 131 * we just check that the expected ones are present. 132 */ helpTestFileObserver(File dir, boolean isEmulated)133 public void helpTestFileObserver(File dir, boolean isEmulated) throws Exception { 134 MockFileObserver fileObserver = null; 135 int[] expected = null; 136 FileEvent[] moveEvents = null; 137 File testFile = new File(dir, TEST_FILE); 138 File testDir = new File(dir, TEST_DIR); 139 File moveDestFile; 140 FileOutputStream out = null; 141 142 fileObserver = new MockFileObserver(testFile.getParentFile()); 143 try { 144 fileObserver.startWatching(); 145 146 verifyTriggeredEventsOnFile(fileObserver, testFile, isEmulated); 147 148 fileObserver.stopWatching(); 149 150 // action after observer stop watching 151 testFile.delete(); // delete 152 153 // should not get any event 154 expected = new int[] {UNDEFINED}; 155 moveEvents = waitForEvent(fileObserver); 156 if (isEmulated) 157 assertEventsContains(testFile, expected, moveEvents); 158 else 159 assertEventsEquals(testFile, expected, moveEvents); 160 } finally { 161 fileObserver.stopWatching(); 162 if (out != null) 163 out.close(); 164 out = null; 165 } 166 fileObserver = new MockFileObserver(testDir); 167 try { 168 fileObserver.startWatching(); 169 verifyTriggeredEventsOnDir(fileObserver, testDir, isEmulated); 170 } finally { 171 fileObserver.stopWatching(); 172 } 173 dir = getContext().getFilesDir(); 174 testFile = new File(dir, TEST_FILE); 175 testFile.createNewFile(); 176 testDir = new File(dir, TEST_DIR); 177 testDir.mkdirs(); 178 moveDestFile = new File(testDir, TEST_FILE); 179 final MockFileObserver movedFileObserver = new MockFileObserver(Arrays.asList( 180 dir, 181 testDir, 182 testFile 183 )); 184 try { 185 movedFileObserver.startWatching(); 186 187 testFile.renameTo(moveDestFile); 188 189 expected = new int[] { 190 FileObserver.MOVED_FROM, 191 FileObserver.MOVED_TO, 192 FileObserver.MOVE_SELF, 193 }; 194 moveEvents = waitForEvent(movedFileObserver); 195 if (isEmulated) { 196 assertEventsContains(testFile, expected, moveEvents); 197 } else { 198 assertEventsEquals(testFile, expected, moveEvents); 199 } 200 } finally { 201 movedFileObserver.stopWatching(); 202 } 203 204 // Because Javadoc didn't specify when should a event happened, 205 // here ACCESS ATTRIB we found no way to test. 206 } 207 verifyTriggeredEventsOnFile(MockFileObserver fileObserver, File testFile, boolean isEmulated)208 private void verifyTriggeredEventsOnFile(MockFileObserver fileObserver, 209 File testFile, boolean isEmulated) throws Exception { 210 final FileOutputStream out = new FileOutputStream(testFile); 211 212 out.write(FILE_DATA); // modify, open, write, modify 213 out.close(); // close_write 214 215 final int[] expected = { 216 FileObserver.MODIFY, 217 FileObserver.OPEN, 218 FileObserver.MODIFY, 219 FileObserver.CLOSE_WRITE 220 }; 221 222 final FileEvent[] moveEvents = waitForEvent(fileObserver); 223 if (isEmulated) { 224 assertEventsContains(testFile, expected, moveEvents); 225 } else { 226 assertEventsEquals(testFile, expected, moveEvents); 227 } 228 } 229 verifyTriggeredEventsOnDir(MockFileObserver fileObserver, File testDir, boolean isEmulated)230 private void verifyTriggeredEventsOnDir(MockFileObserver fileObserver, 231 File testDir, boolean isEmulated) throws Exception { 232 final File testFile = new File(testDir, TEST_FILE); 233 assertTrue(testFile.createNewFile()); 234 assertTrue(testFile.exists()); 235 testFile.delete(); 236 testDir.delete(); 237 238 final int[] expected = { 239 FileObserver.CREATE, 240 FileObserver.OPEN, 241 FileObserver.CLOSE_WRITE, 242 FileObserver.DELETE, 243 FileObserver.DELETE_SELF, 244 UNDEFINED 245 }; 246 247 final FileEvent[] moveEvents = waitForEvent(fileObserver); 248 if (isEmulated) { 249 assertEventsContains(testFile, expected, moveEvents); 250 } else { 251 assertEventsEquals(testFile, expected, moveEvents); 252 } 253 } 254 testFileObserver()255 public void testFileObserver() throws Exception { 256 helpTestFileObserver(getContext().getFilesDir(), false); 257 } 258 259 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserverExternal()260 public void testFileObserverExternal() throws Exception { 261 helpTestFileObserver(getContext().getExternalFilesDir(null), true); 262 } 263 264 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserverExternalStorageDirectory()265 public void testFileObserverExternalStorageDirectory() throws Exception { 266 helpTestFileObserver(EXT_STORAGE_DIR, true); 267 } 268 269 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserver_multipleFilesFull()270 public void testFileObserver_multipleFilesFull() throws Exception { 271 verifyMultipleFiles( 272 Pair.create(getContext().getCacheDir(), false), 273 Pair.create(getContext().getFilesDir(), false), 274 Pair.create(getContext().getExternalFilesDir(null), true), 275 Pair.create(EXT_STORAGE_DIR, true) 276 ); 277 } 278 279 @AppModeInstant(reason = "Instant specific variant excluding disallowed external storage") testFileObserver_multipleFilesInstant()280 public void testFileObserver_multipleFilesInstant() throws Exception { 281 verifyMultipleFiles( 282 Pair.create(getContext().getCacheDir(), false), 283 Pair.create(getContext().getFilesDir(), false) 284 ); 285 } 286 287 @SafeVarargs verifyMultipleFiles(Pair<File, Boolean>.... dirsAndIsEmulated)288 private final void verifyMultipleFiles(Pair<File, Boolean>... dirsAndIsEmulated) throws Exception { 289 List<File> directories = new ArrayList<>(dirsAndIsEmulated.length); 290 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 291 directories.add(pair.first); 292 } 293 294 final MockFileObserver fileObserver1 = new MockFileObserver(directories); 295 try { 296 fileObserver1.startWatching(); 297 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 298 verifyTriggeredEventsOnFile(fileObserver1, 299 new File(pair.first, TEST_FILE), pair.second); 300 } 301 } finally { 302 fileObserver1.stopWatching(); 303 } 304 305 directories = new ArrayList<>(dirsAndIsEmulated.length); 306 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 307 directories.add(new File(pair.first, TEST_DIR)); 308 } 309 310 final MockFileObserver fileObserver2 = new MockFileObserver(directories); 311 try { 312 fileObserver2.startWatching(); 313 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 314 verifyTriggeredEventsOnDir(fileObserver2, 315 new File(pair.first, TEST_DIR), pair.second); 316 } 317 } finally { 318 fileObserver2.stopWatching(); 319 } 320 } 321 assertEventsEquals( File testFile, final int[] expected, final FileEvent[] moveEvents)322 private void assertEventsEquals( 323 File testFile, final int[] expected, final FileEvent[] moveEvents) { 324 List<Integer> expectedEvents = new ArrayList<Integer>(); 325 for (int i = 0; i < expected.length; i++) { 326 expectedEvents.add(expected[i]); 327 } 328 List<FileEvent> actualEvents = Arrays.asList(moveEvents); 329 String message = "For test file [" + testFile.getAbsolutePath() 330 + "] expected: " + expectedEvents + " Actual: " + actualEvents; 331 assertEquals(message, expected.length, moveEvents.length); 332 for (int i = 0; i < expected.length; i++) { 333 assertEquals(message, expected[i], moveEvents[i].event); 334 } 335 } 336 assertEventsContains( File testFile, final int[] expected, final FileEvent[] moveEvents)337 private void assertEventsContains( 338 File testFile, final int[] expected, final FileEvent[] moveEvents) { 339 List<Integer> expectedEvents = new ArrayList<Integer>(); 340 for (int i = 0; i < expected.length; i++) { 341 expectedEvents.add(expected[i]); 342 } 343 List<FileEvent> actualEvents = Arrays.asList(moveEvents); 344 String message = "For test file [" + testFile.getAbsolutePath() 345 + "] expected: " + expectedEvents + " Actual: " + actualEvents; 346 int j = 0; 347 for (int i = 0; i < expected.length; i++) { 348 while (j < moveEvents.length && expected[i] != moveEvents[j].event) j++; 349 if (j >= moveEvents.length) fail(message); 350 j++; 351 } 352 } 353 waitForEvent(MockFileObserver fileObserver)354 private FileEvent[] waitForEvent(MockFileObserver fileObserver) 355 throws InterruptedException { 356 Thread.sleep(DELAY_MSECOND); 357 synchronized (fileObserver) { 358 return fileObserver.getEvents(); 359 } 360 } 361 362 private static class FileEvent { 363 public int event = UNDEFINED; 364 public String path; 365 FileEvent(final int event, final String path)366 public FileEvent(final int event, final String path) { 367 this.event = event; 368 this.path = path; 369 } 370 371 @Override toString()372 public String toString() { 373 return Integer.toString(event); 374 } 375 } 376 377 /* 378 * MockFileObserver 379 */ 380 private static class MockFileObserver extends FileObserver { 381 382 private List<FileEvent> mEvents = new ArrayList<FileEvent>(); 383 MockFileObserver(File file)384 public MockFileObserver(File file) { 385 super(file); 386 } 387 MockFileObserver(File file, int mask)388 public MockFileObserver(File file, int mask) { 389 super(file, mask); 390 } 391 MockFileObserver(List<File> files)392 public MockFileObserver(List<File> files) { 393 super(files); 394 } 395 MockFileObserver(List<File> files, int mask)396 public MockFileObserver(List<File> files, int mask) { 397 super(files, mask); 398 } 399 400 @Override onEvent(int event, String path)401 public synchronized void onEvent(int event, String path) { 402 mEvents.add(new FileEvent(event, path)); 403 } 404 getEvents()405 public synchronized FileEvent[] getEvents() { 406 final FileEvent[] events = new FileEvent[mEvents.size()]; 407 mEvents.toArray(events); 408 mEvents.clear(); 409 return events; 410 } 411 } 412 } 413