• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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