Fix using StrictJarFile that is @hide API
In order to do the unbundle DocumentsUI, it is not appropriate to
use the @hide API. StrictJarFile is @hide API.
StrictJarFile has the 4 gigabytes limitation and common compress
support the archive size above 4 gigabytes
ReadableArchiveTest has the implicit assumption that the sequence
order of rows has the consistentence for using the other archive
library. To remove the assumption and move to JUnit 4 style.
fix some checkstyle warning.
Fixes: 112696623
Fixes: 110868242
Test: atest DocumentsUITests
Change-Id: Ibaf0b2cc80cbd322c09791f2287734022572f1e4
diff --git a/Android.bp b/Android.bp
index 1458d94..fe6b75a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -9,6 +9,7 @@
"androidx.recyclerview_recyclerview",
"androidx.recyclerview_recyclerview-selection",
"androidx.transition_transition",
+ "apache-commons-compress",
"com.google.android.material_material",
"guava",
],
diff --git a/build_apk.mk b/build_apk.mk
index 23e19f2..1c9394c 100644
--- a/build_apk.mk
+++ b/build_apk.mk
@@ -1,7 +1,8 @@
LOCAL_MODULE_TAGS := optional
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_JAVA_LIBRARIES += guava
+LOCAL_STATIC_JAVA_LIBRARIES += guava \
+ apache-commons-compress
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.legacy_legacy-support-core-ui \
diff --git a/src/com/android/documentsui/archives/Archive.java b/src/com/android/documentsui/archives/Archive.java
index e1119df..5193063 100644
--- a/src/com/android/documentsui/archives/Archive.java
+++ b/src/com/android/documentsui/archives/Archive.java
@@ -24,11 +24,8 @@
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
-import android.os.storage.StorageManager;
-import android.provider.DocumentsContract;
-import android.provider.MetadataReader;
import android.provider.DocumentsContract.Document;
-import androidx.annotation.Nullable;
+import android.provider.MetadataReader;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -36,6 +33,7 @@
import android.webkit.MimeTypeMap;
import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
import androidx.core.util.Preconditions;
import java.io.Closeable;
@@ -45,8 +43,8 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.zip.ZipEntry;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
/**
* Provides basic implementation for creating, extracting and accessing
@@ -72,11 +70,11 @@
// The container as well as values are guarded by mEntries.
@GuardedBy("mEntries")
- final Map<String, ZipEntry> mEntries;
+ final Map<String, ZipArchiveEntry> mEntries;
// The container as well as values and elements of values are guarded by mEntries.
@GuardedBy("mEntries")
- final Map<String, List<ZipEntry>> mTree;
+ final Map<String, List<ZipArchiveEntry>> mTree;
Archive(
Context context,
@@ -95,7 +93,7 @@
/**
* Returns a valid, normalized path for an entry.
*/
- public static String getEntryPath(ZipEntry entry) {
+ public static String getEntryPath(ZipArchiveEntry entry) {
Preconditions.checkArgument(entry.isDirectory() == entry.getName().endsWith("/"),
"Ill-formated ZIP-file.");
if (entry.getName().startsWith("/")) {
@@ -138,11 +136,11 @@
}
synchronized (mEntries) {
- final List<ZipEntry> parentList = mTree.get(parsedParentId.mPath);
+ final List<ZipArchiveEntry> parentList = mTree.get(parsedParentId.mPath);
if (parentList == null) {
throw new FileNotFoundException();
}
- for (final ZipEntry entry : parentList) {
+ for (final ZipArchiveEntry entry : parentList) {
addCursorRow(result, entry);
}
}
@@ -160,7 +158,7 @@
"Mismatching archive Uri. Expected: %s, actual: %s.");
synchronized (mEntries) {
- final ZipEntry entry = mEntries.get(parsedId.mPath);
+ final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
if (entry == null) {
throw new FileNotFoundException();
}
@@ -181,12 +179,12 @@
"Mismatching archive Uri. Expected: %s, actual: %s.");
synchronized (mEntries) {
- final ZipEntry entry = mEntries.get(parsedId.mPath);
+ final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
if (entry == null) {
return false;
}
- final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
+ final ZipArchiveEntry parentEntry = mEntries.get(parsedParentId.mPath);
if (parentEntry == null || !parentEntry.isDirectory()) {
return false;
}
@@ -213,7 +211,7 @@
"Mismatching archive Uri. Expected: %s, actual: %s.");
synchronized (mEntries) {
- final ZipEntry entry = mEntries.get(parsedId.mPath);
+ final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
if (entry == null) {
throw new FileNotFoundException();
}
@@ -270,7 +268,7 @@
/**
* Not thread safe.
*/
- void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
+ void addCursorRow(MatrixCursor cursor, ZipArchiveEntry entry) {
final MatrixCursor.RowBuilder row = cursor.newRow();
final ArchiveId parsedId = createArchiveId(getEntryPath(entry));
row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId());
@@ -289,7 +287,7 @@
row.add(Document.COLUMN_FLAGS, flags);
}
- static String getMimeTypeForEntry(ZipEntry entry) {
+ static String getMimeTypeForEntry(ZipArchiveEntry entry) {
if (entry.isDirectory()) {
return Document.MIME_TYPE_DIR;
}
diff --git a/src/com/android/documentsui/archives/Proxy.java b/src/com/android/documentsui/archives/Proxy.java
index d72d309..fcb29e3 100644
--- a/src/com/android/documentsui/archives/Proxy.java
+++ b/src/com/android/documentsui/archives/Proxy.java
@@ -16,28 +16,27 @@
package com.android.documentsui.archives;
+import android.os.FileUtils;
import android.os.ProxyFileDescriptorCallback;
import android.system.ErrnoException;
import android.system.OsConstants;
-import android.util.Log;
-import android.util.jar.StrictJarFile;
import java.io.IOException;
import java.io.InputStream;
-import java.util.zip.ZipEntry;
-import android.os.FileUtils;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
/**
* Provides a backend for a seekable file descriptors for files in archives.
*/
public class Proxy extends ProxyFileDescriptorCallback {
- private final StrictJarFile mFile;
- private final ZipEntry mEntry;
+ private final ZipFile mFile;
+ private final ZipArchiveEntry mEntry;
private InputStream mInputStream = null;
private long mOffset = 0;
- Proxy(StrictJarFile file, ZipEntry entry) throws IOException {
+ Proxy(ZipFile file, ZipArchiveEntry entry) throws IOException {
mFile = file;
mEntry = entry;
recreateInputStream();
@@ -82,7 +81,7 @@
}
return size - remainingSize;
- }
+ }
@Override public void onRelease() {
FileUtils.closeQuietly(mInputStream);
diff --git a/src/com/android/documentsui/archives/ReadableArchive.java b/src/com/android/documentsui/archives/ReadableArchive.java
index e38aaa5..a8a9a92 100644
--- a/src/com/android/documentsui/archives/ReadableArchive.java
+++ b/src/com/android/documentsui/archives/ReadableArchive.java
@@ -16,6 +16,8 @@
package com.android.documentsui.archives;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Point;
@@ -23,33 +25,31 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.OperationCanceledException;
+import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
-import androidx.annotation.Nullable;
import android.util.Log;
-import android.util.jar.StrictJarFile;
-import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
import androidx.core.util.Preconditions;
-import android.os.FileUtils;
-
import java.io.File;
-import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
+import java.util.Enumeration;
import java.util.List;
-import java.util.Set;
import java.util.Stack;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.ZipEntry;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.apache.commons.compress.utils.IOUtils;
/**
* Provides basic implementation for extracting and accessing
@@ -61,12 +61,12 @@
private static final String TAG = "ReadableArchive";
private final StorageManager mStorageManager;
- private final StrictJarFile mZipFile;
+ private final ZipFile mZipFile;
+ private final ParcelFileDescriptor mParcelFileDescriptor;
private ReadableArchive(
Context context,
- @Nullable File file,
- @Nullable FileDescriptor fd,
+ @Nullable ParcelFileDescriptor parcelFileDescriptor,
Uri archiveUri,
int accessMode,
@Nullable Uri notificationUri)
@@ -78,17 +78,18 @@
mStorageManager = mContext.getSystemService(StorageManager.class);
- mZipFile = file != null ?
- new StrictJarFile(file.getPath(), false /* verify */,
- false /* signatures */) :
- new StrictJarFile(fd, false /* verify */, false /* signatures */);
+ if (parcelFileDescriptor == null || parcelFileDescriptor.getFileDescriptor() == null) {
+ throw new IllegalArgumentException("File descriptor is invalid");
+ }
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mZipFile = openArchive(parcelFileDescriptor);
- ZipEntry entry;
+ ZipArchiveEntry entry;
String entryPath;
- final Iterator<ZipEntry> it = mZipFile.iterator();
- final Stack<ZipEntry> stack = new Stack<>();
- while (it.hasNext()) {
- entry = it.next();
+ final Enumeration<ZipArchiveEntry> it = mZipFile.getEntries();
+ final Stack<ZipArchiveEntry> stack = new Stack<>();
+ while (it.hasMoreElements()) {
+ entry = it.nextElement();
if (entry.isDirectory() != entry.getName().endsWith("/")) {
throw new IOException(
"Directories must have a trailing slash, and files must not.");
@@ -99,7 +100,7 @@
}
mEntries.put(entryPath, entry);
if (entry.isDirectory()) {
- mTree.put(entryPath, new ArrayList<ZipEntry>());
+ mTree.put(entryPath, new ArrayList<ZipArchiveEntry>());
}
if (!"/".equals(entryPath)) { // Skip root, as it doesn't have a parent.
stack.push(entry);
@@ -108,8 +109,8 @@
int delimiterIndex;
String parentPath;
- ZipEntry parentEntry;
- List<ZipEntry> parentList;
+ ZipArchiveEntry parentEntry;
+ List<ZipArchiveEntry> parentList;
// Go through all directories recursively and build a tree structure.
while (stack.size() > 0) {
@@ -126,7 +127,7 @@
// The ZIP file doesn't contain all directories leading to the entry.
// It's rare, but can happen in a valid ZIP archive. In such case create a
// fake ZipEntry and add it on top of the stack to process it next.
- parentEntry = new ZipEntry(parentPath);
+ parentEntry = new ZipArchiveEntry(parentPath);
parentEntry.setSize(0);
parentEntry.setTime(entry.getTime());
mEntries.put(parentPath, parentEntry);
@@ -144,40 +145,39 @@
}
/**
+ * To check the access mode is readable.
+ *
* @see ParcelFileDescriptor
*/
public static boolean supportsAccessMode(int accessMode) {
- return accessMode == ParcelFileDescriptor.MODE_READ_ONLY;
+ return accessMode == MODE_READ_ONLY;
}
/**
* Creates a DocumentsArchive instance for opening, browsing and accessing
* documents within the archive passed as a file descriptor.
- *
+ * <p>
* If the file descriptor is not seekable, then a snapshot will be created.
- *
+ * </p><p>
* This method takes ownership for the passed descriptor. The caller must
* not use it after passing.
- *
+ * </p>
* @param context Context of the provider.
* @param descriptor File descriptor for the archive's contents.
* @param archiveUri Uri of the archive document.
* @param accessMode Access mode for the archive {@see ParcelFileDescriptor}.
- * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @param notificationUri notificationUri Uri for notifying that the archive file has changed.
*/
public static ReadableArchive createForParcelFileDescriptor(
Context context, ParcelFileDescriptor descriptor, Uri archiveUri, int accessMode,
@Nullable Uri notificationUri)
throws IOException {
- FileDescriptor fd = null;
- try {
- if (canSeek(descriptor)) {
- fd = new FileDescriptor();
- fd.setInt$(descriptor.detachFd());
- return new ReadableArchive(context, null, fd, archiveUri, accessMode,
- notificationUri);
- }
+ if (canSeek(descriptor)) {
+ return new ReadableArchive(context, descriptor, archiveUri, accessMode,
+ notificationUri);
+ }
+ try {
// Fallback for non-seekable file descriptors.
File snapshotFile = null;
try {
@@ -202,7 +202,11 @@
}
outputStream.flush();
}
- return new ReadableArchive(context, snapshotFile, null, archiveUri, accessMode,
+
+ ParcelFileDescriptor snapshotPfd = ParcelFileDescriptor.open(
+ snapshotFile, MODE_READ_ONLY);
+
+ return new ReadableArchive(context, snapshotPfd, archiveUri, accessMode,
notificationUri);
} finally {
// On UNIX the file will be still available for processes which opened it, even
@@ -215,7 +219,6 @@
// Since the method takes ownership of the passed descriptor, close it
// on exception.
FileUtils.closeQuietly(descriptor);
- FileUtils.closeQuietly(fd);
throw e;
}
}
@@ -230,14 +233,14 @@
MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri,
"Mismatching archive Uri. Expected: %s, actual: %s.");
- final ZipEntry entry = mEntries.get(parsedId.mPath);
+ final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
if (entry == null) {
throw new FileNotFoundException();
}
try {
return mStorageManager.openProxyFileDescriptor(
- ParcelFileDescriptor.MODE_READ_ONLY, new Proxy(mZipFile, entry));
+ MODE_READ_ONLY, new Proxy(mZipFile, entry));
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -253,7 +256,7 @@
Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
"Thumbnails only supported for image/* MIME type.");
- final ZipEntry entry = mEntries.get(parsedId.mPath);
+ final ZipArchiveEntry entry = mEntries.get(parsedId.mPath);
if (entry == null) {
throw new FileNotFoundException();
}
@@ -305,6 +308,35 @@
mZipFile.close();
} catch (IOException e) {
// Silent close.
+ } finally {
+ /**
+ * For creating FileInputStream by using FileDescriptor, the file descriptor will not
+ * be closed after FileInputStream closed.
+ */
+ IOUtils.closeQuietly(mParcelFileDescriptor);
}
}
-};
+
+ private static ZipFile openArchive(ParcelFileDescriptor parcelFileDescriptor)
+ throws IOException {
+ // TODO: To support multiple archive type
+
+ /**
+ * ZipFile keep the FileChannel instance as member field archive. FileChannel doesn't be
+ * closed until ZipFile.close(). FileChannel.close() invoke
+ * AbstractInterruptibleChannel.close() and then FileChannelImpl.implCloseChannel() is
+ * called. FileChannelImpl.implCloseChannel() will close the member field parent that is
+ * FileInputStream and is assigned in FileInputStream.getChannel().
+ * So, to close ZipFile is to close FileInputStream but not file descriptor.
+ */
+ FileChannel fileChannel = new FileInputStream(parcelFileDescriptor.getFileDescriptor())
+ .getChannel();
+ try {
+ return new ZipFile((SeekableByteChannel)fileChannel);
+ } catch (IOException e) {
+ IOUtils.closeQuietly(fileChannel);
+ IOUtils.closeQuietly(parcelFileDescriptor);
+ throw e;
+ }
+ }
+}
diff --git a/src/com/android/documentsui/archives/WriteableArchive.java b/src/com/android/documentsui/archives/WriteableArchive.java
index 0c3e44e..528e8e5 100644
--- a/src/com/android/documentsui/archives/WriteableArchive.java
+++ b/src/com/android/documentsui/archives/WriteableArchive.java
@@ -19,34 +19,31 @@
import android.content.Context;
import android.net.Uri;
import android.os.CancellationSignal;
+import android.os.FileUtils;
import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.provider.DocumentsContract.Document;
-import androidx.annotation.Nullable;
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import android.os.FileUtils;
-
-import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
+
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+
/**
* Provides basic implementation for creating archives.
*
@@ -77,17 +74,17 @@
throw new IllegalStateException("Unsupported access mode.");
}
- addEntry(null /* no parent */, new ZipEntry("/")); // Root entry.
+ addEntry(null /* no parent */, new ZipArchiveEntry("/")); // Root entry.
mOutputStream = new AutoCloseOutputStream(fd);
mZipOutputStream = new ZipOutputStream(mOutputStream);
}
- private void addEntry(@Nullable ZipEntry parentEntry, ZipEntry entry) {
+ private void addEntry(@Nullable ZipArchiveEntry parentEntry, ZipArchiveEntry entry) {
final String entryPath = getEntryPath(entry);
synchronized (mEntries) {
if (entry.isDirectory()) {
if (!mTree.containsKey(entryPath)) {
- mTree.put(entryPath, new ArrayList<ZipEntry>());
+ mTree.put(entryPath, new ArrayList<ZipArchiveEntry>());
}
}
mEntries.put(entryPath, entry);
@@ -115,7 +112,7 @@
* @param descriptor File descriptor for the archive's contents.
* @param archiveUri Uri of the archive document.
* @param accessMode Access mode for the archive {@see ParcelFileDescriptor}.
- * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @param notificationUri notificationUri Uri for notifying that the archive file has changed.
*/
@VisibleForTesting
public static WriteableArchive createForParcelFileDescriptor(
@@ -142,17 +139,18 @@
"Mismatching archive Uri. Expected: %s, actual: %s.");
final boolean isDirectory = Document.MIME_TYPE_DIR.equals(mimeType);
- ZipEntry entry;
+ ZipArchiveEntry entry;
String entryPath;
synchronized (mEntries) {
- final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
+ final ZipArchiveEntry parentEntry = mEntries.get(parsedParentId.mPath);
if (parentEntry == null) {
throw new FileNotFoundException();
}
- if (displayName.indexOf("/") != -1 || ".".equals(displayName) || "..".equals(displayName)) {
+ if (displayName.indexOf("/") != -1 || ".".equals(displayName)
+ || "..".equals(displayName)) {
throw new IllegalStateException("Display name contains invalid characters.");
}
@@ -162,9 +160,10 @@
assert(parentEntry.getName().endsWith("/"));
- final String parentName = "/".equals(parentEntry.getName()) ? "" : parentEntry.getName();
+ final String parentName = "/".equals(parentEntry.getName())
+ ? "" : parentEntry.getName();
final String entryName = parentName + displayName + (isDirectory ? "/" : "");
- entry = new ZipEntry(entryName);
+ entry = new ZipArchiveEntry(entryName);
entryPath = getEntryPath(entry);
entry.setSize(0);
@@ -206,7 +205,7 @@
MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri,
"Mismatching archive Uri. Expected: %s, actual: %s.");
- final ZipEntry entry;
+ final ZipArchiveEntry entry;
synchronized (mEntries) {
entry = mEntries.get(parsedId.mPath);
if (entry == null) {
@@ -313,4 +312,4 @@
FileUtils.closeQuietly(mOutputStream);
}
-};
+}
diff --git a/tests/functional/com/android/documentsui/FileCopyUiTest.java b/tests/functional/com/android/documentsui/FileCopyUiTest.java
index c0cd84f..1203483 100644
--- a/tests/functional/com/android/documentsui/FileCopyUiTest.java
+++ b/tests/functional/com/android/documentsui/FileCopyUiTest.java
@@ -41,13 +41,15 @@
import com.android.documentsui.files.FilesActivity;
import com.android.documentsui.services.TestNotificationService;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
/**
* This class test the below points
@@ -247,20 +249,19 @@
private void loadImageFromResources(Uri root, DocumentsProviderHelper helper, int resId,
Resources res) throws Exception {
- ZipInputStream in = null;
+ ZipArchiveInputStream in = null;
int read = 0;
int count = 0;
try {
- in = new ZipInputStream(res.openRawResource(resId));
- ZipEntry zipEntry = null;
- while ((zipEntry = in.getNextEntry()) != null && (count++ < TARGET_COUNT)) {
- String fileName = zipEntry.getName();
+ in = new ZipArchiveInputStream(res.openRawResource(resId));
+ ArchiveEntry archiveEntry = null;
+ while ((archiveEntry = in.getNextEntry()) != null && (count++ < TARGET_COUNT)) {
+ String fileName = archiveEntry.getName();
Uri uri = helper.createDocument(root, "image/png", fileName);
byte[] buff = new byte[1024];
while ((read = in.read(buff)) > 0) {
helper.writeAppendDocument(uri, buff);
}
- in.closeEntry();
buff = null;
}
} finally {
diff --git a/tests/unit/com/android/documentsui/archives/ReadableArchiveTest.java b/tests/unit/com/android/documentsui/archives/ReadableArchiveTest.java
index 98e4f09..49878db 100644
--- a/tests/unit/com/android/documentsui/archives/ReadableArchiveTest.java
+++ b/tests/unit/com/android/documentsui/archives/ReadableArchiveTest.java
@@ -16,32 +16,38 @@
package com.android.documentsui.archives;
-import com.android.documentsui.archives.ReadableArchive;
-import com.android.documentsui.tests.R;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
-import java.io.File;
-import java.io.FileOutputStream;
+import com.android.documentsui.tests.R;
+
import java.io.IOException;
-import java.io.InputStream;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
@MediumTest
-public class ReadableArchiveTest extends AndroidTestCase {
+public class ReadableArchiveTest {
private static final Uri ARCHIVE_URI = Uri.parse("content://i/love/strawberries");
private static final String NOTIFICATION_URI =
"content://com.android.documentsui.archives/notification-uri";
@@ -49,29 +55,27 @@
private Archive mArchive = null;
private TestUtils mTestUtils = null;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
mExecutor = Executors.newSingleThreadExecutor();
mTestUtils = new TestUtils(InstrumentationRegistry.getTargetContext(),
InstrumentationRegistry.getContext(), mExecutor);
}
- @Override
+ @After
public void tearDown() throws Exception {
mExecutor.shutdown();
assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS));
if (mArchive != null) {
mArchive.close();
}
- super.tearDown();
}
public static ArchiveId createArchiveId(String path) {
return new ArchiveId(ARCHIVE_URI, ParcelFileDescriptor.MODE_READ_ONLY, path);
}
- public void loadArchive(ParcelFileDescriptor descriptor) throws IOException {
+ private void loadArchive(ParcelFileDescriptor descriptor) throws IOException {
mArchive = ReadableArchive.createForParcelFileDescriptor(
InstrumentationRegistry.getTargetContext(),
descriptor,
@@ -80,15 +84,30 @@
Uri.parse(NOTIFICATION_URI));
}
+ private static void assertRowExist(Cursor cursor, String targetDocId) {
+ assertTrue(cursor.moveToFirst());
+
+ boolean found = false;
+ final int count = cursor.getCount();
+ for (int i = 0; i < count; i++) {
+ cursor.moveToPosition(i);
+ if (TextUtils.equals(targetDocId, cursor.getString(
+ cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)))) {
+ found = true;
+ break;
+ }
+ }
+
+ assertTrue(targetDocId + " should be exists", found);
+ }
+
+ @Test
public void testQueryChildDocument() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
final Cursor cursor = mArchive.queryChildDocuments(
createArchiveId("/").toDocumentId(), null, null);
- assertTrue(cursor.moveToFirst());
- assertEquals(
- createArchiveId("/file1.txt").toDocumentId(),
- cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertRowExist(cursor, createArchiveId("/file1.txt").toDocumentId());
assertEquals("file1.txt",
cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
assertEquals("text/plain",
@@ -96,9 +115,7 @@
assertEquals(13,
cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
- assertTrue(cursor.moveToNext());
- assertEquals(createArchiveId("/dir1/").toDocumentId(),
- cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertRowExist(cursor, createArchiveId("/dir1/").toDocumentId());
assertEquals("dir1",
cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
assertEquals(Document.MIME_TYPE_DIR,
@@ -106,10 +123,7 @@
assertEquals(0,
cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
- assertTrue(cursor.moveToNext());
- assertEquals(
- createArchiveId("/dir2/").toDocumentId(),
- cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertRowExist(cursor, createArchiveId("/dir2/").toDocumentId());
assertEquals("dir2",
cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
assertEquals(Document.MIME_TYPE_DIR,
@@ -117,7 +131,6 @@
assertEquals(0,
cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
- assertFalse(cursor.moveToNext());
// Check if querying children works too.
final Cursor childCursor = mArchive.queryChildDocuments(
@@ -138,6 +151,7 @@
childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
}
+ @Test
public void testQueryChildDocument_NoDirs() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.no_dirs));
final Cursor cursor = mArchive.queryChildDocuments(
@@ -185,6 +199,7 @@
assertFalse(childCursor2.moveToNext());
}
+ @Test
public void testQueryChildDocument_EmptyDirs() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.empty_dirs));
final Cursor cursor = mArchive.queryChildDocuments(
@@ -205,11 +220,7 @@
final Cursor childCursor = mArchive.queryChildDocuments(
createArchiveId("/dir1/").toDocumentId(), null, null);
- assertTrue(childCursor.moveToFirst());
- assertEquals(
- createArchiveId("/dir1/dir2/").toDocumentId(),
- childCursor.getString(childCursor.getColumnIndexOrThrow(
- Document.COLUMN_DOCUMENT_ID)));
+ assertRowExist(childCursor, createArchiveId("/dir1/dir2/").toDocumentId());
assertEquals("dir2",
childCursor.getString(childCursor.getColumnIndexOrThrow(
Document.COLUMN_DISPLAY_NAME)));
@@ -219,7 +230,7 @@
assertEquals(0,
childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
- assertTrue(childCursor.moveToNext());
+ assertRowExist(childCursor, createArchiveId("/dir1/dir3/").toDocumentId());
assertEquals(
createArchiveId("/dir1/dir3/").toDocumentId(),
childCursor.getString(childCursor.getColumnIndexOrThrow(
@@ -232,7 +243,6 @@
Document.COLUMN_MIME_TYPE)));
assertEquals(0,
childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
- assertFalse(cursor.moveToNext());
final Cursor childCursor2 = mArchive.queryChildDocuments(
createArchiveId("/dir1/dir2/").toDocumentId(),
@@ -245,6 +255,7 @@
assertFalse(childCursor3.moveToFirst());
}
+ @Test
public void testGetDocumentType() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType(
@@ -253,6 +264,7 @@
createArchiveId("/file1.txt").toDocumentId()));
}
+ @Test
public void testIsChildDocument() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
final String documentId = createArchiveId("/").toDocumentId();
@@ -267,6 +279,7 @@
createArchiveId("/dir1/cherries.txt").toDocumentId()));
}
+ @Test
public void testQueryDocument() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
final Cursor cursor = mArchive.queryDocument(
@@ -285,11 +298,13 @@
cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
}
+ @Test
public void testOpenDocument() throws IOException, ErrnoException {
loadArchive(mTestUtils.getSeekableDescriptor(R.raw.archive));
commonTestOpenDocument();
}
+ @Test
public void testOpenDocument_NonSeekable() throws IOException, ErrnoException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
commonTestOpenDocument();
@@ -310,11 +325,13 @@
}
}
+ @Test
public void testCanSeek() throws IOException {
assertTrue(Archive.canSeek(mTestUtils.getSeekableDescriptor(R.raw.archive)));
assertFalse(Archive.canSeek(mTestUtils.getNonSeekableDescriptor(R.raw.archive)));
}
+ @Test
public void testBrokenArchive() throws IOException {
loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive));
final Cursor cursor = mArchive.queryChildDocuments(