Loading Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ java_defaults { "androidx.recyclerview_recyclerview", "androidx.recyclerview_recyclerview-selection", "androidx.transition_transition", "apache-commons-compress", "com.google.android.material_material", "guava", ], Loading build_apk.mk +2 −1 Original line number Diff line number Diff line 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 \ Loading src/com/android/documentsui/archives/Archive.java +15 −17 Original line number Diff line number Diff line Loading @@ -24,11 +24,8 @@ import android.graphics.Point; 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; Loading @@ -36,6 +33,7 @@ import android.text.TextUtils; import android.webkit.MimeTypeMap; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.core.util.Preconditions; import java.io.Closeable; Loading @@ -45,8 +43,8 @@ import java.util.HashMap; 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 Loading @@ -72,11 +70,11 @@ public abstract class Archive implements Closeable { // 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, Loading @@ -95,7 +93,7 @@ public abstract class Archive implements Closeable { /** * 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("/")) { Loading Loading @@ -138,11 +136,11 @@ public abstract class Archive implements Closeable { } 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); } } Loading @@ -160,7 +158,7 @@ public abstract class Archive implements Closeable { "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(); } Loading @@ -181,12 +179,12 @@ public abstract class Archive implements Closeable { "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; } Loading @@ -213,7 +211,7 @@ public abstract class Archive implements Closeable { "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(); } Loading Loading @@ -270,7 +268,7 @@ public abstract class Archive implements Closeable { /** * 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()); Loading @@ -289,7 +287,7 @@ public abstract class Archive implements Closeable { row.add(Document.COLUMN_FLAGS, flags); } static String getMimeTypeForEntry(ZipEntry entry) { static String getMimeTypeForEntry(ZipArchiveEntry entry) { if (entry.isDirectory()) { return Document.MIME_TYPE_DIR; } Loading src/com/android/documentsui/archives/Proxy.java +7 −8 Original line number Diff line number Diff line Loading @@ -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(); Loading src/com/android/documentsui/archives/ReadableArchive.java +79 −47 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -23,33 +25,31 @@ import android.media.ExifInterface; 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 Loading @@ -61,12 +61,12 @@ public class ReadableArchive extends Archive { 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) Loading @@ -78,17 +78,18 @@ public class ReadableArchive extends Archive { 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."); Loading @@ -99,7 +100,7 @@ public class ReadableArchive extends Archive { } 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); Loading @@ -108,8 +109,8 @@ public class ReadableArchive extends Archive { 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) { Loading @@ -126,7 +127,7 @@ public class ReadableArchive extends Archive { // 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); Loading @@ -144,40 +145,39 @@ public class ReadableArchive extends Archive { } /** * 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, return new ReadableArchive(context, descriptor, archiveUri, accessMode, notificationUri); } try { // Fallback for non-seekable file descriptors. File snapshotFile = null; try { Loading @@ -202,7 +202,11 @@ public class ReadableArchive extends Archive { } 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 Loading @@ -215,7 +219,6 @@ public class ReadableArchive extends Archive { // Since the method takes ownership of the passed descriptor, close it // on exception. FileUtils.closeQuietly(descriptor); FileUtils.closeQuietly(fd); throw e; } } Loading @@ -230,14 +233,14 @@ public class ReadableArchive extends Archive { 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); } Loading @@ -253,7 +256,7 @@ public class ReadableArchive extends Archive { 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(); } Loading Loading @@ -305,6 +308,35 @@ public class ReadableArchive extends Archive { 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; } } } }; Loading
Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ java_defaults { "androidx.recyclerview_recyclerview", "androidx.recyclerview_recyclerview-selection", "androidx.transition_transition", "apache-commons-compress", "com.google.android.material_material", "guava", ], Loading
build_apk.mk +2 −1 Original line number Diff line number Diff line 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 \ Loading
src/com/android/documentsui/archives/Archive.java +15 −17 Original line number Diff line number Diff line Loading @@ -24,11 +24,8 @@ import android.graphics.Point; 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; Loading @@ -36,6 +33,7 @@ import android.text.TextUtils; import android.webkit.MimeTypeMap; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.core.util.Preconditions; import java.io.Closeable; Loading @@ -45,8 +43,8 @@ import java.util.HashMap; 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 Loading @@ -72,11 +70,11 @@ public abstract class Archive implements Closeable { // 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, Loading @@ -95,7 +93,7 @@ public abstract class Archive implements Closeable { /** * 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("/")) { Loading Loading @@ -138,11 +136,11 @@ public abstract class Archive implements Closeable { } 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); } } Loading @@ -160,7 +158,7 @@ public abstract class Archive implements Closeable { "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(); } Loading @@ -181,12 +179,12 @@ public abstract class Archive implements Closeable { "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; } Loading @@ -213,7 +211,7 @@ public abstract class Archive implements Closeable { "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(); } Loading Loading @@ -270,7 +268,7 @@ public abstract class Archive implements Closeable { /** * 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()); Loading @@ -289,7 +287,7 @@ public abstract class Archive implements Closeable { row.add(Document.COLUMN_FLAGS, flags); } static String getMimeTypeForEntry(ZipEntry entry) { static String getMimeTypeForEntry(ZipArchiveEntry entry) { if (entry.isDirectory()) { return Document.MIME_TYPE_DIR; } Loading
src/com/android/documentsui/archives/Proxy.java +7 −8 Original line number Diff line number Diff line Loading @@ -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(); Loading
src/com/android/documentsui/archives/ReadableArchive.java +79 −47 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -23,33 +25,31 @@ import android.media.ExifInterface; 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 Loading @@ -61,12 +61,12 @@ public class ReadableArchive extends Archive { 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) Loading @@ -78,17 +78,18 @@ public class ReadableArchive extends Archive { 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."); Loading @@ -99,7 +100,7 @@ public class ReadableArchive extends Archive { } 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); Loading @@ -108,8 +109,8 @@ public class ReadableArchive extends Archive { 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) { Loading @@ -126,7 +127,7 @@ public class ReadableArchive extends Archive { // 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); Loading @@ -144,40 +145,39 @@ public class ReadableArchive extends Archive { } /** * 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, return new ReadableArchive(context, descriptor, archiveUri, accessMode, notificationUri); } try { // Fallback for non-seekable file descriptors. File snapshotFile = null; try { Loading @@ -202,7 +202,11 @@ public class ReadableArchive extends Archive { } 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 Loading @@ -215,7 +219,6 @@ public class ReadableArchive extends Archive { // Since the method takes ownership of the passed descriptor, close it // on exception. FileUtils.closeQuietly(descriptor); FileUtils.closeQuietly(fd); throw e; } } Loading @@ -230,14 +233,14 @@ public class ReadableArchive extends Archive { 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); } Loading @@ -253,7 +256,7 @@ public class ReadableArchive extends Archive { 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(); } Loading Loading @@ -305,6 +308,35 @@ public class ReadableArchive extends Archive { 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; } } } };