blob: e758dd4a7cf36e34d81c03757a9d12c352f1b3f4 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os.cts;
import android.os.FileObserver;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
import android.test.AndroidTestCase;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FileObserverTest extends AndroidTestCase {
private static final String PATH = "/PATH";
private static final String TEST_FILE = "file_observer_test.txt";
private static final String TEST_DIR = "fileobserver_dir";
private static final int FILE_DATA = 0x20;
private static final int UNDEFINED = 0x8000;
private static final long DELAY_MSECOND = 2000;
private void helpSetUp(File dir) throws Exception {
File testFile = new File(dir, TEST_FILE);
testFile.createNewFile();
File testDir = new File(dir, TEST_DIR);
testDir.mkdirs();
}
@Override
protected void setUp() throws Exception {
super.setUp();
File dir = getContext().getFilesDir();
helpSetUp(dir);
dir = getContext().getCacheDir();
helpSetUp(dir);
// Instant apps cannot access external storage
if (!InstrumentationRegistry.getTargetContext().getPackageManager().isInstantApp()) {
dir = getContext().getExternalFilesDir(null);
helpSetUp(dir);
}
}
private void helpTearDown(File dir) throws Exception {
File testFile = new File(dir, TEST_FILE);
File testDir = new File(dir, TEST_DIR);
File moveDestFile = new File(testDir, TEST_FILE);
if (testFile.exists()) {
testFile.delete();
}
if (moveDestFile.exists()) {
moveDestFile.delete();
}
if (testDir.exists()) {
testDir.delete();
}
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
File dir = getContext().getFilesDir();
helpTearDown(dir);
dir = getContext().getCacheDir();
helpTearDown(dir);
dir = getContext().getExternalFilesDir(null);
helpTearDown(dir);
}
public void testConstructor() {
// new the instance
new MockFileObserver(new File(PATH));
// new the instance
new MockFileObserver(new File(PATH), FileObserver.ACCESS);
}
/*
* Test point
* 1. Observe a dir, when it's child file have been written and closed,
* observer should get modify open-child modify-child and closed-write events.
* 2. While stop observer a dir, observer should't get any event while delete it's child file.
* 3. Observer a dir, when create delete a child file and delete self,
* observer should get create-child close-nowrite delete-child delete-self events.
* 4. Observer a file, the file moved from dir and the file moved to dir, move the file,
* file observer should get move-self event,
* moved from dir observer should get moved-from event,
* moved to dir observer should get moved-to event.
*
* On emulated storage, there may be additional operations related to case insensitivity, so
* we just check that the expected ones are present.
*/
public void helpTestFileObserver(File dir, boolean isEmulated) throws Exception {
MockFileObserver fileObserver = null;
int[] expected = null;
FileEvent[] moveEvents = null;
File testFile = new File(dir, TEST_FILE);
File testDir = new File(dir, TEST_DIR);
File moveDestFile;
FileOutputStream out = null;
fileObserver = new MockFileObserver(testFile.getParentFile());
try {
fileObserver.startWatching();
verifyTriggeredEventsOnFile(fileObserver, testFile, isEmulated);
fileObserver.stopWatching();
// action after observer stop watching
testFile.delete(); // delete
// should not get any event
expected = new int[] {UNDEFINED};
moveEvents = waitForEvent(fileObserver);
if (isEmulated)
assertEventsContains(expected, moveEvents);
else
assertEventsEquals(expected, moveEvents);
} finally {
fileObserver.stopWatching();
if (out != null)
out.close();
out = null;
}
fileObserver = new MockFileObserver(testDir);
try {
fileObserver.startWatching();
verifyTriggeredEventsOnDir(fileObserver, testDir, isEmulated);
} finally {
fileObserver.stopWatching();
}
dir = getContext().getFilesDir();
testFile = new File(dir, TEST_FILE);
testFile.createNewFile();
testDir = new File(dir, TEST_DIR);
testDir.mkdirs();
moveDestFile = new File(testDir, TEST_FILE);
final MockFileObserver movedFileObserver = new MockFileObserver(Arrays.asList(
dir,
testDir,
testFile
));
try {
movedFileObserver.startWatching();
testFile.renameTo(moveDestFile);
expected = new int[] {
FileObserver.MOVED_FROM,
FileObserver.MOVED_TO,
FileObserver.MOVE_SELF,
};
moveEvents = waitForEvent(movedFileObserver);
if (isEmulated) {
assertEventsContains(expected, moveEvents);
} else {
assertEventsEquals(expected, moveEvents);
}
} finally {
movedFileObserver.stopWatching();
}
// Because Javadoc didn't specify when should a event happened,
// here ACCESS ATTRIB we found no way to test.
}
private void verifyTriggeredEventsOnFile(MockFileObserver fileObserver,
File testFile, boolean isEmulated) throws Exception {
final FileOutputStream out = new FileOutputStream(testFile);
out.write(FILE_DATA); // modify, open, write, modify
out.close(); // close_write
final int[] expected = {
FileObserver.MODIFY,
FileObserver.OPEN,
FileObserver.MODIFY,
FileObserver.CLOSE_WRITE
};
final FileEvent[] moveEvents = waitForEvent(fileObserver);
if (isEmulated) {
assertEventsContains(expected, moveEvents);
} else {
assertEventsEquals(expected, moveEvents);
}
}
private void verifyTriggeredEventsOnDir(MockFileObserver fileObserver,
File testDir, boolean isEmulated) throws Exception {
final File testFile = new File(testDir, TEST_FILE);
assertTrue(testFile.createNewFile());
assertTrue(testFile.exists());
testFile.delete();
testDir.delete();
final int[] expected = {
FileObserver.CREATE,
FileObserver.OPEN,
FileObserver.CLOSE_WRITE,
FileObserver.DELETE,
FileObserver.DELETE_SELF,
UNDEFINED
};
final FileEvent[] moveEvents = waitForEvent(fileObserver);
if (isEmulated) {
assertEventsContains(expected, moveEvents);
} else {
assertEventsEquals(expected, moveEvents);
}
}
public void testFileObserver() throws Exception {
helpTestFileObserver(getContext().getFilesDir(), false);
}
@AppModeFull(reason = "Instant apps cannot access external storage")
public void testFileObserverExternal() throws Exception {
helpTestFileObserver(getContext().getExternalFilesDir(null), true);
}
@AppModeFull(reason = "Instant apps cannot access external storage")
public void testFileObserver_multipleFilesFull() throws Exception {
verifyMultipleFiles(
Pair.create(getContext().getCacheDir(), false),
Pair.create(getContext().getFilesDir(), false),
Pair.create(getContext().getExternalFilesDir(null), true)
);
}
@AppModeInstant(reason = "Instant specific variant excluding disallowed external storage")
public void testFileObserver_multipleFilesInstant() throws Exception {
verifyMultipleFiles(
Pair.create(getContext().getCacheDir(), false),
Pair.create(getContext().getFilesDir(), false)
);
}
@SafeVarargs
private final void verifyMultipleFiles(Pair<File, Boolean>... dirsAndIsEmulated) throws Exception {
List<File> directories = new ArrayList<>(dirsAndIsEmulated.length);
for (Pair<File, Boolean> pair : dirsAndIsEmulated) {
directories.add(pair.first);
}
final MockFileObserver fileObserver1 = new MockFileObserver(directories);
try {
fileObserver1.startWatching();
for (Pair<File, Boolean> pair : dirsAndIsEmulated) {
verifyTriggeredEventsOnFile(fileObserver1,
new File(pair.first, TEST_FILE), pair.second);
}
} finally {
fileObserver1.stopWatching();
}
directories = new ArrayList<>(dirsAndIsEmulated.length);
for (Pair<File, Boolean> pair : dirsAndIsEmulated) {
directories.add(new File(pair.first, TEST_DIR));
}
final MockFileObserver fileObserver2 = new MockFileObserver(directories);
try {
fileObserver2.startWatching();
for (Pair<File, Boolean> pair : dirsAndIsEmulated) {
verifyTriggeredEventsOnDir(fileObserver2,
new File(pair.first, TEST_DIR), pair.second);
}
} finally {
fileObserver2.stopWatching();
}
}
private void assertEventsEquals(final int[] expected, final FileEvent[] moveEvents) {
List<Integer> expectedEvents = new ArrayList<Integer>();
for (int i = 0; i < expected.length; i++) {
expectedEvents.add(expected[i]);
}
List<FileEvent> actualEvents = Arrays.asList(moveEvents);
String message = "Expected: " + expectedEvents + " Actual: " + actualEvents;
assertEquals(message, expected.length, moveEvents.length);
for (int i = 0; i < expected.length; i++) {
assertEquals(message, expected[i], moveEvents[i].event);
}
}
private void assertEventsContains(final int[] expected, final FileEvent[] moveEvents) {
List<Integer> expectedEvents = new ArrayList<Integer>();
for (int i = 0; i < expected.length; i++) {
expectedEvents.add(expected[i]);
}
List<FileEvent> actualEvents = Arrays.asList(moveEvents);
String message = "Expected to contain: " + expectedEvents + " Actual: " + actualEvents;
int j = 0;
for (int i = 0; i < expected.length; i++) {
while (expected[i] != moveEvents[j].event) {
j++;
if (j >= moveEvents.length)
fail(message);
}
j++;
}
}
private FileEvent[] waitForEvent(MockFileObserver fileObserver)
throws InterruptedException {
Thread.sleep(DELAY_MSECOND);
synchronized (fileObserver) {
return fileObserver.getEvents();
}
}
private static class FileEvent {
public int event = UNDEFINED;
public String path;
public FileEvent(final int event, final String path) {
this.event = event;
this.path = path;
}
@Override
public String toString() {
return Integer.toString(event);
}
}
/*
* MockFileObserver
*/
private static class MockFileObserver extends FileObserver {
private List<FileEvent> mEvents = new ArrayList<FileEvent>();
public MockFileObserver(File file) {
super(file);
}
public MockFileObserver(File file, int mask) {
super(file, mask);
}
public MockFileObserver(List<File> files) {
super(files);
}
public MockFileObserver(List<File> files, int mask) {
super(files, mask);
}
@Override
public synchronized void onEvent(int event, String path) {
mEvents.add(new FileEvent(event, path));
}
public synchronized FileEvent[] getEvents() {
final FileEvent[] events = new FileEvent[mEvents.size()];
mEvents.toArray(events);
mEvents.clear();
return events;
}
}
}