DocumentArchive.java revision 64ce8c2e2085a0d5ff3e69ba5520873d41c76af5
164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski/* 264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Copyright (C) 2015 The Android Open Source Project 364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Licensed under the Apache License, Version 2.0 (the "License"); 564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * you may not use this file except in compliance with the License. 664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * You may obtain a copy of the License at 764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * http://www.apache.org/licenses/LICENSE-2.0 964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 1064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Unless required by applicable law or agreed to in writing, software 1164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * distributed under the License is distributed on an "AS IS" BASIS, 1264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * See the License for the specific language governing permissions and 1464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * limitations under the License. 1564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 1664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 1764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskipackage android.support.provider; 1864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 1964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.content.Context; 2064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.database.Cursor; 2164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.database.MatrixCursor; 2264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.net.Uri; 2364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.os.CancellationSignal; 2464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 2564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.provider.DocumentsContract.Document; 2664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.provider.DocumentsProvider; 2764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.util.Log; 2864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport android.webkit.MimeTypeMap; 2964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 3064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.Closeable; 3164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.File; 3264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.FileNotFoundException; 3364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.FileOutputStream; 3464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.IOException; 3564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.io.InputStream; 3664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.lang.IllegalArgumentException; 3764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.lang.IllegalStateException; 3864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.lang.UnsupportedOperationException; 3964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.Collections; 4064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.Iterator; 4164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.concurrent.ExecutorService; 4264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.concurrent.Executors; 4364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.zip.ZipEntry; 4464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.zip.ZipFile; 4564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskiimport java.util.zip.ZipInputStream; 4664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 4764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski/** 4864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Provides basic implementation for creating, extracting and accessing 4964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * files within archives exposed by a document provider. The id delimiter 5064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * must be a character which is not used in document ids generated by the 5164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * document provider. 5264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 5364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * <p>This class is thread safe. 5464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 5564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @hide 5664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 5764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewskipublic class DocumentArchive implements Closeable { 5864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private static final String TAG = "DocumentArchive"; 5964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 6064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private static final String[] DEFAULT_PROJECTION = new String[] { 6164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Document.COLUMN_DOCUMENT_ID, 6264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Document.COLUMN_DISPLAY_NAME, 6364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Document.COLUMN_MIME_TYPE, 6464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Document.COLUMN_SIZE 6564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski }; 6664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 6764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final Context mContext; 6864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final String mDocumentId; 6964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final char mIdDelimiter; 7064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final Uri mNotificationUri; 7164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final ZipFile mZipFile; 7264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private final ExecutorService mExecutor; 7364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 7464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private DocumentArchive( 7564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Context context, 7664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski File file, 7764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski String documentId, 7864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski char idDelimiter, 7964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Uri notificationUri) 8064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throws IOException { 8164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mContext = context; 8264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mDocumentId = documentId; 8364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mIdDelimiter = idDelimiter; 8464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mNotificationUri = notificationUri; 8564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mZipFile = new ZipFile(file); 8664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mExecutor = Executors.newSingleThreadExecutor(); 8764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 8864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 8964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 9064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Creates a DocumentsArchive instance for opening, browsing and accessing 9164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * documents within the archive passed as a local file. 9264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 9364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param context Context of the provider. 9464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param File Local file containing the archive. 9564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param documentId ID of the archive document. 9664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param idDelimiter Delimiter for constructing IDs of documents within the archive. 9764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * The delimiter must never be used for IDs of other documents. 9864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param Uri notificationUri Uri for notifying that the archive file has changed. 9964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see createForParcelFileDescriptor(DocumentsProvider, ParcelFileDescriptor, String, char, 10064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Uri) 10164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 10264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public static DocumentArchive createForLocalFile( 10364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Context context, File file, String documentId, char idDelimiter, Uri notificationUri) 10464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throws IOException { 10564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return new DocumentArchive(context, file, documentId, idDelimiter, notificationUri); 10664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 10764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 10864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 10964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Creates a DocumentsArchive instance for opening, browsing and accessing 11064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * documents within the archive passed as a file descriptor. 11164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 11264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * <p>Note, that this method should be used only if the document does not exist 11364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * on the local storage. A snapshot file will be created, which may be slower 11464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * and consume significant resources, in contrast to using 11564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * {@see createForLocalFile(Context, File, String, char, Uri}. 11664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 11764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param context Context of the provider. 11864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param descriptor File descriptor for the archive's contents. 11964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param documentId ID of the archive document. 12064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param idDelimiter Delimiter for constructing IDs of documents within the archive. 12164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * The delimiter must never be used for IDs of other documents. 12264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @param Uri notificationUri Uri for notifying that the archive file has changed. 12364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see createForLocalFile(Context, File, String, char, Uri) 12464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 12564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public static DocumentArchive createForParcelFileDescriptor( 12664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Context context, ParcelFileDescriptor descriptor, String documentId, 12764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski char idDelimiter, Uri notificationUri) 12864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throws IOException { 12964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski File snapshotFile = null; 13064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try { 13164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // Create a copy of the archive, as ZipFile doesn't operate on streams. 13264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // Moreover, ZipInputStream would be inefficient for large files on 13364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // pipes. 13464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski snapshotFile = File.createTempFile("android.support.provider.snapshot{", 13564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "}.zip", context.getCacheDir()); 13664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 13764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try ( 13864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final FileOutputStream outputStream = 13964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski new ParcelFileDescriptor.AutoCloseOutputStream( 14064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski ParcelFileDescriptor.open( 14164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); 14264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParcelFileDescriptor.AutoCloseInputStream inputStream = 14364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski new ParcelFileDescriptor.AutoCloseInputStream(descriptor); 14464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski ) { 14564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final byte[] buffer = new byte[32 * 1024]; 14664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski int bytes; 14764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski while ((bytes = inputStream.read(buffer)) != -1) { 14864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski outputStream.write(buffer, 0, bytes); 14964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 15064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski outputStream.flush(); 15164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return new DocumentArchive(context, snapshotFile, documentId, idDelimiter, 15264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski notificationUri); 15364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 15464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } finally { 15564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // On UNIX the file will be still available for processes which opened it, even 15664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // after deleting it. Remove it ASAP, as it won't be used by anyone else. 15764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (snapshotFile != null) { 15864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski snapshotFile.delete(); 15964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 16064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 16164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 16264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 16364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 16464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Lists child documents of an archive or a directory within an 16564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * archive. Must be called only for archives with supported mime type, 16664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * or for documents within archives. 16764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 16864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see DocumentsProvider.queryChildDocuments(String, String[], String) 16964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 17064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) { 17164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId( 17264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski documentId, mIdDelimiter); 17364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId, 17464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Mismatching document ID. Expected: %s, actual: %s."); 17564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 17664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final String parentPath = parsedParentId.mPath != null ? normalizePath( 17764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski parsedParentId.mPath, true /* isDirectory */) : "/"; 17864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final MatrixCursor result = new MatrixCursor( 17964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski projection != null ? projection : DEFAULT_PROJECTION); 18064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (mNotificationUri != null) { 18164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); 18264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 18364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 18464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski File file; 18564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski String maybeParentPath; 18664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // TODO: Build an in-memory tree for storing the directory structure. 18764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski for (final ZipEntry entry : Collections.list(mZipFile.entries())) { 18864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski file = new File(getPathForEntry(entry)); 18964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski maybeParentPath = normalizePath(file.getParent(), true /* isDirectory */); 19064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (maybeParentPath.equals(parentPath)) { 19164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski addCursorRow(result, entry); 19264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 19364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 19464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return result; 19564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 19664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 19764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 19864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Returns a MIME type of a document within an archive. 19964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 20064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see DocumentsProvider.getDocumentType(String) 20164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 20264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public String getDocumentType(String documentId) throws FileNotFoundException { 20364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 20464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski documentId, mIdDelimiter); 20564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 20664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Mismatching document ID. Expected: %s, actual: %s."); 20764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 20864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 20964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 21064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (entry == null) { 21164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throw new FileNotFoundException(); 21264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 21364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return getMimeTypeForEntry(entry); 21464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 21564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 21664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 21764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Returns true if a document within an archive is a child or any descendant of the archive 21864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * document or another document within the archive. 21964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 22064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see DocumentsProvider.isChildDocument(String, String) 22164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 22264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public boolean isChildDocument(String parentDocumentId, String documentId) { 22364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId( 22464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski parentDocumentId, mIdDelimiter); 22564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 22664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski documentId, mIdDelimiter); 22764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId, 22864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Mismatching document ID. Expected: %s, actual: %s."); 22964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentNotNull(parsedId.mPath, 23064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Not a document within an archive."); 23164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 23264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 23364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (entry == null) { 23464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return false; 23564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 23664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 23764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (parsedParentId.mPath == null) { 23864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // No need to compare paths. Every file in the archive is a child of the archive 23964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // file. 24064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return true; 24164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 24264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 24364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ZipEntry parentEntry = mZipFile.getEntry(parsedParentId.mPath); 24464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (parentEntry == null || !parentEntry.isDirectory()) { 24564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return false; 24664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 24764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 24864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final String parentPath = getPathForEntry(parentEntry); 24964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 25064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // Add a trailing slash even if it's not a directory, so it's easy to check if the 25164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // entry is a descendant. 25264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final String pathWithSlash = normalizePath(getPathForEntry(entry), true /* isDirectory */); 25364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash); 25464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 25564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 25664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 25764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Returns metadata of a document within an archive. 25864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 25964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see DocumentsProvider.queryDocument(String, String[]) 26064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 26164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public Cursor queryDocument(String documentId, String[] projection) 26264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throws FileNotFoundException { 26364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 26464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski documentId, mIdDelimiter); 26564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 26664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Mismatching document ID. Expected: %s, actual: %s."); 26764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 26864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 26964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 27064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (entry == null) { 27164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throw new FileNotFoundException(); 27264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 27364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 27464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final MatrixCursor result = new MatrixCursor( 27564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski projection != null ? projection : DEFAULT_PROJECTION); 27664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (mNotificationUri != null) { 27764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); 27864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 27964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski addCursorRow(result, entry); 28064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return result; 28164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 28264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 28364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 28464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Opens a file within an archive. 28564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 28664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * @see DocumentsProvider.openDocument(String, String, CancellationSignal)) 28764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 28864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public ParcelFileDescriptor openDocument( 28964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski String documentId, String mode, final CancellationSignal signal) 29064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throws FileNotFoundException { 29164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals("r", mode, 29264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Invalid mode. Only reading \"r\" supported, but got: \"%s\"."); 29364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 29464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski documentId, mIdDelimiter); 29564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 29664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski "Mismatching document ID. Expected: %s, actual: %s."); 29764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 29864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 29964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 30064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (entry == null) { 30164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throw new FileNotFoundException(); 30264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 30364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 30464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski ParcelFileDescriptor[] pipe; 30564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski InputStream inputStream = null; 30664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try { 30764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski pipe = ParcelFileDescriptor.createReliablePipe(); 30864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski inputStream = mZipFile.getInputStream(entry); 30964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } catch (IOException e) { 31064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (inputStream != null) { 31164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski IoUtils.closeQuietly(inputStream); 31264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 31364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // Ideally we'd simply throw IOException to the caller, but for consistency 31464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // with DocumentsProvider::openDocument, converting it to IllegalStateException. 31564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throw new IllegalStateException("Failed to open the document.", e); 31664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 31764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParcelFileDescriptor outputPipe = pipe[1]; 31864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final InputStream finalInputStream = inputStream; 31964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mExecutor.execute( 32064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski new Runnable() { 32164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski @Override 32264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public void run() { 32364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream = 32464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) { 32564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try { 32664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final byte buffer[] = new byte[32 * 1024]; 32764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski int bytes; 32864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski while ((bytes = finalInputStream.read(buffer)) != -1) { 32964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (Thread.interrupted()) { 33064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski throw new InterruptedException(); 33164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 33264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (signal != null) { 33364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski signal.throwIfCanceled(); 33464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 33564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski outputStream.write(buffer, 0, bytes); 33664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 33764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } catch (IOException | InterruptedException e) { 33864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // Catch the exception before the outer try-with-resource closes the 33964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // pipe with close() instead of closeWithError(). 34064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski try { 34164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski outputPipe.closeWithError(e.getMessage()); 34264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } catch (IOException e2) { 34364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Log.e(TAG, "Failed to close the pipe after an error.", e2); 34464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 34564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 34664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } catch (IOException e) { 34764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski Log.e(TAG, "Failed to close the output stream gracefully.", e); 34864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } finally { 34964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski IoUtils.closeQuietly(finalInputStream); 35064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 35164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 35264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski }); 35364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 35464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return pipe[0]; 35564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 35664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 35764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski /** 35864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * Schedules a gracefully close of the archive after any opened files are closed. 35964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * 36064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * <p>This method does not block until shutdown. Once called, other methods should not be 36164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski * called. 36264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski */ 36364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski @Override 36464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public void close() { 36564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mExecutor.execute(new Runnable() { 36664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski @Override 36764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski public void run() { 36864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski IoUtils.closeQuietly(mZipFile); 36964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 37064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski }); 37164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski mExecutor.shutdown(); 37264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 37364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 37464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private void addCursorRow(MatrixCursor cursor, ZipEntry entry) { 37564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final MatrixCursor.RowBuilder row = cursor.newRow(); 37664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final ParsedDocumentId parsedId = new ParsedDocumentId(mDocumentId, entry.getName()); 37764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId(mIdDelimiter)); 37864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final File file = new File(entry.getName()); 37964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 38064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski row.add(Document.COLUMN_SIZE, entry.getSize()); 38164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski row.add(Document.COLUMN_MIME_TYPE, getMimeTypeForEntry(entry)); 38264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 38364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 38464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private String getMimeTypeForEntry(ZipEntry entry) { 38564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (entry.isDirectory()) { 38664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return Document.MIME_TYPE_DIR; 38764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 38864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 38964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final int lastDot = entry.getName().lastIndexOf('.'); 39064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (lastDot >= 0) { 39164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final String extension = entry.getName().substring(lastDot + 1).toLowerCase(); 39264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 39364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (mimeType != null) { 39464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return mimeType; 39564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 39664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 39764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 39864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return "application/octet-stream"; 39964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 40064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 40164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private static String normalizePath(String path, boolean isDirectory) { 40264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski // TODO: Add support for different path separators. 40364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski final StringBuilder result = new StringBuilder(); 40464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (!path.startsWith("/")) { 40564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski result.append("/"); 40664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 40764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski result.append(path); 40864ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski if (isDirectory && result.length() > 1 && !path.endsWith("/")) { 40964ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski result.append("/"); 41064ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 41164ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return result.toString(); 41264ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 41364ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski 41464ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski private static String getPathForEntry(ZipEntry entry) { 41564ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski return normalizePath(entry.getName(), entry.isDirectory()); 41664ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski } 41764ce8c2e2085a0d5ff3e69ba5520873d41c76af5Tomasz Mikolajewski}; 418