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 * 133 * On internal storage, there may also be additional operations - the art runtime creates a 134 * folder called 'oat_primary' in the cache folder, for instance. So it is best just to 135 * always check that the expected ones are present, since we cannot stop other components 136 * from performing extra actions. 137 */ helpTestFileObserver(File dir, boolean isEmulated)138 public void helpTestFileObserver(File dir, boolean isEmulated) throws Exception { 139 MockFileObserver fileObserver = null; 140 int[] expected = null; 141 FileEvent[] moveEvents = null; 142 File testFile = new File(dir, TEST_FILE); 143 File testDir = new File(dir, TEST_DIR); 144 File moveDestFile; 145 FileOutputStream out = null; 146 147 fileObserver = new MockFileObserver(testFile.getParentFile()); 148 try { 149 fileObserver.startWatching(); 150 151 verifyTriggeredEventsOnFile(fileObserver, testFile, isEmulated); 152 153 fileObserver.stopWatching(); 154 155 // action after observer stop watching 156 testFile.delete(); // delete 157 158 // should not get any event 159 expected = new int[] {UNDEFINED}; 160 moveEvents = waitForEvent(fileObserver); 161 assertEventsContains(testFile, expected, moveEvents); 162 } finally { 163 fileObserver.stopWatching(); 164 if (out != null) 165 out.close(); 166 out = null; 167 } 168 fileObserver = new MockFileObserver(testDir); 169 try { 170 fileObserver.startWatching(); 171 verifyTriggeredEventsOnDir(fileObserver, testDir, isEmulated); 172 } finally { 173 fileObserver.stopWatching(); 174 } 175 dir = getContext().getFilesDir(); 176 testFile = new File(dir, TEST_FILE); 177 testFile.createNewFile(); 178 testDir = new File(dir, TEST_DIR); 179 testDir.mkdirs(); 180 moveDestFile = new File(testDir, TEST_FILE); 181 final MockFileObserver movedFileObserver = new MockFileObserver(Arrays.asList( 182 dir, 183 testDir, 184 testFile 185 )); 186 try { 187 movedFileObserver.startWatching(); 188 189 testFile.renameTo(moveDestFile); 190 191 expected = new int[] { 192 FileObserver.MOVED_FROM, 193 FileObserver.MOVED_TO, 194 FileObserver.MOVE_SELF, 195 }; 196 moveEvents = waitForEvent(movedFileObserver); 197 assertEventsContains(testFile, expected, moveEvents); 198 } finally { 199 movedFileObserver.stopWatching(); 200 } 201 202 // Because Javadoc didn't specify when should a event happened, 203 // here ACCESS ATTRIB we found no way to test. 204 } 205 verifyTriggeredEventsOnFile(MockFileObserver fileObserver, File testFile, boolean isEmulated)206 private void verifyTriggeredEventsOnFile(MockFileObserver fileObserver, 207 File testFile, boolean isEmulated) throws Exception { 208 final FileOutputStream out = new FileOutputStream(testFile); 209 210 out.write(FILE_DATA); // modify, open, write, modify 211 out.close(); // close_write 212 213 final int[] expected = { 214 FileObserver.MODIFY, 215 FileObserver.OPEN, 216 FileObserver.MODIFY, 217 FileObserver.CLOSE_WRITE 218 }; 219 220 final FileEvent[] moveEvents = waitForEvent(fileObserver); 221 assertEventsContains(testFile, expected, moveEvents); 222 } 223 verifyTriggeredEventsOnDir(MockFileObserver fileObserver, File testDir, boolean isEmulated)224 private void verifyTriggeredEventsOnDir(MockFileObserver fileObserver, 225 File testDir, boolean isEmulated) throws Exception { 226 final File testFile = new File(testDir, TEST_FILE); 227 assertTrue(testFile.createNewFile()); 228 assertTrue(testFile.exists()); 229 testFile.delete(); 230 testDir.delete(); 231 232 final int[] expected = { 233 FileObserver.CREATE, 234 FileObserver.OPEN, 235 FileObserver.CLOSE_WRITE, 236 FileObserver.DELETE, 237 FileObserver.DELETE_SELF, 238 UNDEFINED 239 }; 240 241 final FileEvent[] moveEvents = waitForEvent(fileObserver); 242 assertEventsContains(testFile, expected, moveEvents); 243 } 244 testFileObserver()245 public void testFileObserver() throws Exception { 246 helpTestFileObserver(getContext().getFilesDir(), false); 247 } 248 249 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserverExternal()250 public void testFileObserverExternal() throws Exception { 251 helpTestFileObserver(getContext().getExternalFilesDir(null), true); 252 } 253 254 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserverExternalStorageDirectory()255 public void testFileObserverExternalStorageDirectory() throws Exception { 256 helpTestFileObserver(EXT_STORAGE_DIR, true); 257 } 258 259 @AppModeFull(reason = "Instant apps cannot access external storage") testFileObserver_multipleFilesFull()260 public void testFileObserver_multipleFilesFull() throws Exception { 261 verifyMultipleFiles( 262 Pair.create(getContext().getCacheDir(), false), 263 Pair.create(getContext().getFilesDir(), false), 264 Pair.create(getContext().getExternalFilesDir(null), true), 265 Pair.create(EXT_STORAGE_DIR, true) 266 ); 267 } 268 269 @AppModeInstant(reason = "Instant specific variant excluding disallowed external storage") testFileObserver_multipleFilesInstant()270 public void testFileObserver_multipleFilesInstant() throws Exception { 271 verifyMultipleFiles( 272 Pair.create(getContext().getCacheDir(), false), 273 Pair.create(getContext().getFilesDir(), false) 274 ); 275 } 276 277 @SafeVarargs verifyMultipleFiles(Pair<File, Boolean>.... dirsAndIsEmulated)278 private final void verifyMultipleFiles(Pair<File, Boolean>... dirsAndIsEmulated) throws Exception { 279 List<File> directories = new ArrayList<>(dirsAndIsEmulated.length); 280 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 281 directories.add(pair.first); 282 } 283 284 final MockFileObserver fileObserver1 = new MockFileObserver(directories); 285 try { 286 fileObserver1.startWatching(); 287 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 288 verifyTriggeredEventsOnFile(fileObserver1, 289 new File(pair.first, TEST_FILE), pair.second); 290 } 291 } finally { 292 fileObserver1.stopWatching(); 293 } 294 295 directories = new ArrayList<>(dirsAndIsEmulated.length); 296 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 297 directories.add(new File(pair.first, TEST_DIR)); 298 } 299 300 final MockFileObserver fileObserver2 = new MockFileObserver(directories); 301 try { 302 fileObserver2.startWatching(); 303 for (Pair<File, Boolean> pair : dirsAndIsEmulated) { 304 verifyTriggeredEventsOnDir(fileObserver2, 305 new File(pair.first, TEST_DIR), pair.second); 306 } 307 } finally { 308 fileObserver2.stopWatching(); 309 } 310 } 311 assertEventsContains( File testFile, final int[] expected, final FileEvent[] moveEvents)312 private void assertEventsContains( 313 File testFile, final int[] expected, final FileEvent[] moveEvents) { 314 List<Integer> expectedEvents = new ArrayList<Integer>(); 315 for (int i = 0; i < expected.length; i++) { 316 expectedEvents.add(expected[i]); 317 } 318 List<FileEvent> actualEvents = Arrays.asList(moveEvents); 319 String message = "For test file [" + testFile.getAbsolutePath() 320 + "] expected: " + expectedEvents + " Actual: " + actualEvents; 321 int j = 0; 322 for (int i = 0; i < expected.length; i++) { 323 while (j < moveEvents.length && expected[i] != moveEvents[j].event) j++; 324 if (j >= moveEvents.length) fail(message); 325 j++; 326 } 327 } 328 waitForEvent(MockFileObserver fileObserver)329 private FileEvent[] waitForEvent(MockFileObserver fileObserver) 330 throws InterruptedException { 331 Thread.sleep(DELAY_MSECOND); 332 synchronized (fileObserver) { 333 return fileObserver.getEvents(); 334 } 335 } 336 337 private static class FileEvent { 338 public int event = UNDEFINED; 339 public String path; 340 FileEvent(final int event, final String path)341 public FileEvent(final int event, final String path) { 342 this.event = event; 343 this.path = path; 344 } 345 346 @Override toString()347 public String toString() { 348 return Integer.toString(event); 349 } 350 } 351 352 /* 353 * MockFileObserver 354 */ 355 private static class MockFileObserver extends FileObserver { 356 357 private List<FileEvent> mEvents = new ArrayList<FileEvent>(); 358 MockFileObserver(File file)359 public MockFileObserver(File file) { 360 super(file); 361 } 362 MockFileObserver(File file, int mask)363 public MockFileObserver(File file, int mask) { 364 super(file, mask); 365 } 366 MockFileObserver(List<File> files)367 public MockFileObserver(List<File> files) { 368 super(files); 369 } 370 MockFileObserver(List<File> files, int mask)371 public MockFileObserver(List<File> files, int mask) { 372 super(files, mask); 373 } 374 375 @Override onEvent(int event, String path)376 public synchronized void onEvent(int event, String path) { 377 mEvents.add(new FileEvent(event, path)); 378 } 379 getEvents()380 public synchronized FileEvent[] getEvents() { 381 final FileEvent[] events = new FileEvent[mEvents.size()]; 382 mEvents.toArray(events); 383 mEvents.clear(); 384 return events; 385 } 386 } 387 } 388