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