DocumentArchive.java revision 09deb05d980153aa6d1a3696aca74f49d46caa94
15f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes/* 25f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * Copyright (C) 2015 The Android Open Source Project 35f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 45f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * Licensed under the Apache License, Version 2.0 (the "License"); 55f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * you may not use this file except in compliance with the License. 65f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * You may obtain a copy of the License at 75f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 85f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * http://www.apache.org/licenses/LICENSE-2.0 95f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 105f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * Unless required by applicable law or agreed to in writing, software 115f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * distributed under the License is distributed on an "AS IS" BASIS, 125f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * See the License for the specific language governing permissions and 145f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * limitations under the License. 155f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes */ 165f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 1754e7df1896a4066cbb9fe6f72249829f0b8c49c6Elliott Hughespackage android.support.provider; 185f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 19f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartierimport android.content.Context; 20f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartierimport android.content.res.AssetFileDescriptor; 21f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartierimport android.database.Cursor; 2208fc03ae5dded4adc9b45b7014a4b9dfedbe95a6Elliott Hughesimport android.database.MatrixCursor; 2308fc03ae5dded4adc9b45b7014a4b9dfedbe95a6Elliott Hughesimport android.graphics.Point; 24e401d146407d61eeb99f8d6176b2ac13c4df1e33Mathieu Chartierimport android.net.Uri; 2576b6167407c2b6f5d40ad895b2793a6b037f54b2Elliott Hughesimport android.os.CancellationSignal; 261aa246dec5abe212f699de1413a0c4a191ca364aElliott Hughesimport android.os.OperationCanceledException; 2780afd02024d20e60b197d3adfbb43cc303cf29e0Vladimir Markoimport android.os.ParcelFileDescriptor; 2833dc7717cd16592bcc825350bea6305be9eb2ea1jeffhaoimport android.provider.DocumentsContract.Document; 294f6ad8ab428038129b2d0d6c40b7fd625cca15e1Ian Rogersimport android.provider.DocumentsProvider; 3008fc03ae5dded4adc9b45b7014a4b9dfedbe95a6Elliott Hughesimport android.support.annotation.Nullable; 31d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport android.util.Log; 324f6ad8ab428038129b2d0d6c40b7fd625cca15e1Ian Rogersimport android.webkit.MimeTypeMap; 3305f3057d6a4d23d712092ccd36a531590bff323bIan Rogers 342dd0e2cea360bc9206eb88ecc40d259e796c239dIan Rogersimport java.io.Closeable; 3500f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogersimport java.io.File; 365f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.io.FileNotFoundException; 378e4aac52962d54cb4be2078b9cd95685e067133aElliott Hughesimport java.io.FileOutputStream; 3808fc03ae5dded4adc9b45b7014a4b9dfedbe95a6Elliott Hughesimport java.io.IOException; 39044288fb8f69e154862763a6b3bfea2bac1b397eElliott Hughesimport java.io.InputStream; 405f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.util.ArrayList; 415f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.lang.IllegalArgumentException; 425f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.lang.IllegalStateException; 43b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartierimport java.lang.UnsupportedOperationException; 44b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartierimport java.util.Collections; 455f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.util.HashMap; 46d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.Iterator; 47d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.List; 48d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.Map; 495f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.util.concurrent.ExecutorService; 50d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.concurrent.Executors; 51d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.zip.ZipEntry; 52d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogersimport java.util.zip.ZipFile; 535f79133a435ebcb20000370d56046fe01201dd80Elliott Hughesimport java.util.zip.ZipInputStream; 54d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 55d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers/** 56d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * Provides basic implementation for creating, extracting and accessing 575f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * files within archives exposed by a document provider. The id delimiter 58d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * must be a character which is not used in document ids generated by the 59d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * document provider. 6054e7df1896a4066cbb9fe6f72249829f0b8c49c6Elliott Hughes * 615f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * <p>This class is thread safe. 625f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 635f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * @hide 645f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes */ 655f79133a435ebcb20000370d56046fe01201dd80Elliott Hughespublic class DocumentArchive implements Closeable { 665f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes private static final String TAG = "DocumentArchive"; 67d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 68d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers private static final String[] DEFAULT_PROJECTION = new String[] { 69d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers Document.COLUMN_DOCUMENT_ID, 705f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes Document.COLUMN_DISPLAY_NAME, 7154e7df1896a4066cbb9fe6f72249829f0b8c49c6Elliott Hughes Document.COLUMN_MIME_TYPE, 722cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier Document.COLUMN_SIZE, 73fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes Document.COLUMN_FLAGS 7432d6e1e5654433d7eadede89e1c770b2c839aee9Elliott Hughes }; 75fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes 762cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier private final Context mContext; 77fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private final String mDocumentId; 78fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private final char mIdDelimiter; 79fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private final Uri mNotificationUri; 80fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private final ZipFile mZipFile; 81fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private final ExecutorService mExecutor; 824dd9b4d95eec9db5338fb9bf132f9bb8facf6cf4Elliott Hughes private final Map<String, List<ZipEntry>> mTree; 83fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes 84fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes private DocumentArchive( 8532d6e1e5654433d7eadede89e1c770b2c839aee9Elliott Hughes Context context, 8632d6e1e5654433d7eadede89e1c770b2c839aee9Elliott Hughes File file, 87ef7d42fca18c16fbaf103822ad16f23246e2905dIan Rogers String documentId, 8800f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers char idDelimiter, 89d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers @Nullable Uri notificationUri) 9046bc778f1feed02b20d25e3d03470c93ca2c0506Mathieu Chartier throws IOException { 9100f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers mContext = context; 925f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes mDocumentId = documentId; 9394f7b49578b6aaa80de8ffed230648d601393905Hiroshi Yamauchi mIdDelimiter = idDelimiter; 942cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier mNotificationUri = notificationUri; 95ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier mZipFile = new ZipFile(file); 962cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier mExecutor = Executors.newSingleThreadExecutor(); 97ef7d42fca18c16fbaf103822ad16f23246e2905dIan Rogers 9874240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe // Build the tree structure in memory. 9974240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe mTree = new HashMap<String, List<ZipEntry>>(); 10074240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe mTree.put("/", new ArrayList<ZipEntry>()); 10174240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe 10274240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe // Traversing entries via ZipFile.entries() is very slow, so copy the entries to a temporary 10374240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe // map for building the tree. 10474240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe final Map<String, ZipEntry> entries = new HashMap<String, ZipEntry>(); 10574240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe for (final ZipEntry entry : Collections.list(mZipFile.entries())) { 10674240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe entries.put(entry.getName(), entry); 10774240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe if (entry.isDirectory()) { 10874240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe mTree.put(entry.getName(), new ArrayList<ZipEntry>()); 10974240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe } 11074240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe } 11174240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe 11274240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe int delimiterIndex; 11374240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe List<ZipEntry> parentList; 11474240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe String parentPath; 11574240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe for (final ZipEntry entry : entries.values()) { 11694f7b49578b6aaa80de8ffed230648d601393905Hiroshi Yamauchi delimiterIndex = entry.getName().lastIndexOf('/', entry.isDirectory() 1172cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier ? entry.getName().length() - 2 : entry.getName().length() - 1); 11874240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe parentPath = 1192cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier delimiterIndex != -1 ? entry.getName().substring(0, delimiterIndex) + "/" : "/"; 12074240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe parentList = mTree.get(parentPath); 12174240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe if (parentList != null) { 12274240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe parentList.add(entry); 12374240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe } else { 12474240819ae09e29b2753ef38f4eb4be1c2762e2eAndreas Gampe Log.w(TAG, "Archived files without a parent are not supported: " + entry.getName()); 125d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 126d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 127ef7d42fca18c16fbaf103822ad16f23246e2905dIan Rogers } 128ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier 129d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers /** 130d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * Creates a DocumentsArchive instance for opening, browsing and accessing 1314e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * documents within the archive passed as a local file. 1324e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * 1333e5cf305db800b2989ad57b7cde8fb3cc9fa1b9eIan Rogers * @param context Context of the provider. 1344e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * @param File Local file containing the archive. 1354e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * @param documentId ID of the archive document. 1364e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * @param idDelimiter Delimiter for constructing IDs of documents within the archive. 1374e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * The delimiter must never be used for IDs of other documents. 1383e5cf305db800b2989ad57b7cde8fb3cc9fa1b9eIan Rogers * @param Uri notificationUri Uri for notifying that the archive file has changed. 1394e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * @see createForParcelFileDescriptor(DocumentsProvider, ParcelFileDescriptor, String, char, 1404e6a31eb97f22f4480827474b30b9e64f396eaceMathieu Chartier * Uri) 141d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers */ 142d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers public static DocumentArchive createForLocalFile( 143ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier Context context, File file, String documentId, char idDelimiter, 14400f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers @Nullable Uri notificationUri) 1454cba0d979a11f955e6ec3c0f1bf61478af7aa810Hiroshi Yamauchi throws IOException { 146ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier return new DocumentArchive(context, file, documentId, idDelimiter, notificationUri); 147ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier } 148ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier 149ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier /** 150ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * Creates a DocumentsArchive instance for opening, browsing and accessing 151ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * documents within the archive passed as a file descriptor. 152ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * 1533e5cf305db800b2989ad57b7cde8fb3cc9fa1b9eIan Rogers * <p>Note, that this method should be used only if the document does not exist 154ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * on the local storage. A snapshot file will be created, which may be slower 155ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * and consume significant resources, in contrast to using 156ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * {@see createForLocalFile(Context, File, String, char, Uri}. 157ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * 158ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * @param context Context of the provider. 159ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * @param descriptor File descriptor for the archive's contents. 160ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * @param documentId ID of the archive document. 161ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * @param idDelimiter Delimiter for constructing IDs of documents within the archive. 162ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * The delimiter must never be used for IDs of other documents. 163ad2541a59c00c2c69e8973088891a2b5257c9780Mathieu Chartier * @param Uri notificationUri Uri for notifying that the archive file has changed. 164590fee9e8972f872301c2d16a575d579ee564beeMathieu Chartier * @see createForLocalFile(Context, File, String, char, Uri) 165590fee9e8972f872301c2d16a575d579ee564beeMathieu Chartier */ 166590fee9e8972f872301c2d16a575d579ee564beeMathieu Chartier public static DocumentArchive createForParcelFileDescriptor( 167590fee9e8972f872301c2d16a575d579ee564beeMathieu Chartier Context context, ParcelFileDescriptor descriptor, String documentId, 168d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers char idDelimiter, @Nullable Uri notificationUri) 169e15ea086439b41a805d164d2beb07b4ba96aaa97Hiroshi Yamauchi throws IOException { 170d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers File snapshotFile = null; 171228602f562f1d130d06e60a98752d99c2d467d6aIan Rogers try { 17200f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers // Create a copy of the archive, as ZipFile doesn't operate on streams. 1739728f91a63016136261231ff5213bde703bd27b6Mathieu Chartier // Moreover, ZipInputStream would be inefficient for large files on 1746ec8ebd178ed39aa09e4c7fad194900114c4121aAndreas Gampe // pipes. 1756ec8ebd178ed39aa09e4c7fad194900114c4121aAndreas Gampe snapshotFile = File.createTempFile("android.support.provider.snapshot{", 1766ec8ebd178ed39aa09e4c7fad194900114c4121aAndreas Gampe "}.zip", context.getCacheDir()); 17700f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers 178d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers try ( 1795f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final FileOutputStream outputStream = 1805f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes new ParcelFileDescriptor.AutoCloseOutputStream( 1815f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes ParcelFileDescriptor.open( 182590fee9e8972f872301c2d16a575d579ee564beeMathieu Chartier snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); 1835f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final ParcelFileDescriptor.AutoCloseInputStream inputStream = 1845f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes new ParcelFileDescriptor.AutoCloseInputStream(descriptor); 1855f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes ) { 1865f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final byte[] buffer = new byte[32 * 1024]; 1872cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier int bytes; 188dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers while ((bytes = inputStream.read(buffer)) != -1) { 1892cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier outputStream.write(buffer, 0, bytes); 1905f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 1915f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes outputStream.flush(); 1925f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes return new DocumentArchive(context, snapshotFile, documentId, idDelimiter, 1935f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes notificationUri); 1945f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 1955f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } finally { 196dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers // On UNIX the file will be still available for processes which opened it, even 197dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers // after deleting it. Remove it ASAP, as it won't be used by anyone else. 1985f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes if (snapshotFile != null) { 199dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers snapshotFile.delete(); 2005f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 2015f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 2025f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 2035f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 2042cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier /** 2052cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier * Lists child documents of an archive or a directory within an 2065f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * archive. Must be called only for archives with supported mime type, 2075f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * or for documents within archives. 2085f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 209dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers * @see DocumentsProvider.queryChildDocuments(String, String[], String) 210dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers */ 2115f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes public Cursor queryChildDocuments(String documentId, @Nullable String[] projection, 2125f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes @Nullable String sortOrder) { 2135f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId( 2145f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes documentId, mIdDelimiter); 2152cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId, 216dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers "Mismatching document ID. Expected: %s, actual: %s."); 217dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers 218dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers final String parentPath = parsedParentId.mPath != null ? parsedParentId.mPath : "/"; 2195f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final MatrixCursor result = new MatrixCursor( 2205f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes projection != null ? projection : DEFAULT_PROJECTION); 221dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers if (mNotificationUri != null) { 2225f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); 2235f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 2245f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 2256aa3df965395566ed6a4fec4af37c2b7577992e9Mathieu Chartier final List<ZipEntry> parentList = mTree.get(parentPath); 22694f7b49578b6aaa80de8ffed230648d601393905Hiroshi Yamauchi Preconditions.checkArgumentNotNull( 2276aa3df965395566ed6a4fec4af37c2b7577992e9Mathieu Chartier parentList, "The requested directory does not exist in the archive."); 2286aa3df965395566ed6a4fec4af37c2b7577992e9Mathieu Chartier for (final ZipEntry entry : parentList) { 2295f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes addCursorRow(result, entry); 230d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 231d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers return result; 232440e4ceb310349ee8eb569495bc04d3d7fbe71cbMathieu Chartier } 233d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 234d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers /** 235d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * Returns a MIME type of a document within an archive. 236d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * 237d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * @see DocumentsProvider.getDocumentType(String) 238d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers */ 239fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes public String getDocumentType(String documentId) throws FileNotFoundException { 240d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 241d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers documentId, mIdDelimiter); 242d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 243d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers "Mismatching document ID. Expected: %s, actual: %s."); 244fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 245d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 246d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 247b894a19dfd668b6779de939cf5265b7e409d8809Xin Guan if (entry == null) { 248e401d146407d61eeb99f8d6176b2ac13c4df1e33Mathieu Chartier throw new FileNotFoundException(); 249d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 250440e4ceb310349ee8eb569495bc04d3d7fbe71cbMathieu Chartier return getMimeTypeForEntry(entry); 251b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier } 252440e4ceb310349ee8eb569495bc04d3d7fbe71cbMathieu Chartier 253d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers /** 254a6e7f0872c42009ecbee82d7fbe452deef9ae65bMathieu Chartier * Returns true if a document within an archive is a child or any descendant of the archive 255d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * document or another document within the archive. 256d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers * 2572cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier * @see DocumentsProvider.isChildDocument(String, String) 2582cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier */ 259f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier public boolean isChildDocument(String parentDocumentId, String documentId) { 260f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId( 261f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier parentDocumentId, mIdDelimiter); 262f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 263f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier documentId, mIdDelimiter); 264f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId, 265d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers "Mismatching document ID. Expected: %s, actual: %s."); 266d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers Preconditions.checkArgumentNotNull(parsedId.mPath, 267d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers "Not a document within an archive."); 268d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 269d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 270d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers if (entry == null) { 271d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers return false; 272d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 273d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 274d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers if (parsedParentId.mPath == null) { 275d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers // No need to compare paths. Every file in the archive is a child of the archive 276d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers // file. 277d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers return true; 278d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 279b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier 280b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier final ZipEntry parentEntry = mZipFile.getEntry(parsedParentId.mPath); 281b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier if (parentEntry == null || !parentEntry.isDirectory()) { 282b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier return false; 283b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier } 284b9001abff3a45f1ae90536da7dd1ec28a6ae0174Mathieu Chartier 285d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers final String parentPath = entry.getName(); 286d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 287d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers // Add a trailing slash even if it's not a directory, so it's easy to check if the 288f0dc8b5519102b3d3e738aed846975ae4239421eMathieu Chartier // entry is a descendant. 289fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes final String pathWithSlash = entry.isDirectory() ? entry.getName() : entry.getName() + "/"; 2905f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash); 291a6e7f0872c42009ecbee82d7fbe452deef9ae65bMathieu Chartier } 292d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 293440e4ceb310349ee8eb569495bc04d3d7fbe71cbMathieu Chartier /** 294fc86162ce2a3467acb690e18cc8bd9b3daafc606Elliott Hughes * Returns metadata of a document within an archive. 2955f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 2965f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * @see DocumentsProvider.queryDocument(String, String[]) 2976d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers */ 2986d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers public Cursor queryDocument(String documentId, @Nullable String[] projection) 2996d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers throws FileNotFoundException { 30000f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 30190443477f9a0061581c420775ce3b7eeae7468bcMathieu Chartier documentId, mIdDelimiter); 3026d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 3036d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers "Mismatching document ID. Expected: %s, actual: %s."); 30462d6c772205b8859f0ebf7ad105402ec4c3e2e01Ian Rogers Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 3050aa50ce2fb75bfc2e815a0c33adf9b049561923bNicolas Geoffray 306d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 30764277f38032208a0c1081ff9e466c04009b96383Brian Carlstrom if (entry == null) { 30862d6c772205b8859f0ebf7ad105402ec4c3e2e01Ian Rogers throw new FileNotFoundException(); 309d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 31014691c5e786e8c2c5734f687e4c96217340771beNicolas Geoffray 31164277f38032208a0c1081ff9e466c04009b96383Brian Carlstrom final MatrixCursor result = new MatrixCursor( 3126d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers projection != null ? projection : DEFAULT_PROJECTION); 3136d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers if (mNotificationUri != null) { 3146d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers result.setNotificationUri(mContext.getContentResolver(), mNotificationUri); 315d423741f91526cada9d081c64ee295ec9863032dElliott Hughes } 3162cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier addCursorRow(result, entry); 3172cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier return result; 318d423741f91526cada9d081c64ee295ec9863032dElliott Hughes } 319d423741f91526cada9d081c64ee295ec9863032dElliott Hughes 320d423741f91526cada9d081c64ee295ec9863032dElliott Hughes /** 321d423741f91526cada9d081c64ee295ec9863032dElliott Hughes * Opens a file within an archive. 322d423741f91526cada9d081c64ee295ec9863032dElliott Hughes * 323d423741f91526cada9d081c64ee295ec9863032dElliott Hughes * @see DocumentsProvider.openDocument(String, String, CancellationSignal)) 324d423741f91526cada9d081c64ee295ec9863032dElliott Hughes */ 3252dd0e2cea360bc9206eb88ecc40d259e796c239dIan Rogers public ParcelFileDescriptor openDocument( 326ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes String documentId, String mode, @Nullable final CancellationSignal signal) 3272cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier throws FileNotFoundException { 328ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes Preconditions.checkArgumentEquals("r", mode, 329ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes "Invalid mode. Only reading \"r\" supported, but got: \"%s\"."); 330ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId( 331ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes documentId, mIdDelimiter); 332ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 333ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes "Mismatching document ID. Expected: %s, actual: %s."); 33450b35e2fd1a68cd1240e4a9d9f363e11764957d1Ian Rogers Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 335ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes 3362cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 337ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes if (entry == null) { 338ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes throw new FileNotFoundException(); 339ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes } 340ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes 341ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes ParcelFileDescriptor[] pipe; 3422cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier InputStream inputStream = null; 3432cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier try { 3446d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers pipe = ParcelFileDescriptor.createReliablePipe(); 3456d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers inputStream = mZipFile.getInputStream(entry); 346ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes } catch (IOException e) { 347ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes if (inputStream != null) { 3486d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers IoUtils.closeQuietly(inputStream); 3496d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } 3506d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers // Ideally we'd simply throw IOException to the caller, but for consistency 3516d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers // with DocumentsProvider::openDocument, converting it to IllegalStateException. 352ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes throw new IllegalStateException("Failed to open the document.", e); 353ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes } 354ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes final ParcelFileDescriptor outputPipe = pipe[1]; 3556d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers final InputStream finalInputStream = inputStream; 3566d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers mExecutor.execute( 3572cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier new Runnable() { 3586d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers @Override 3596d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers public void run() { 3606d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream = 361ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) { 362ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes try { 363ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes final byte buffer[] = new byte[32 * 1024]; 3646d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers int bytes; 3656d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers while ((bytes = finalInputStream.read(buffer)) != -1) { 3666d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers if (Thread.interrupted()) { 3676d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers throw new InterruptedException(); 3686d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } 369ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes if (signal != null) { 370ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes signal.throwIfCanceled(); 371ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes } 372ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes outputStream.write(buffer, 0, bytes); 3736d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } 3746d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } catch (IOException | InterruptedException e) { 3756d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers // Catch the exception before the outer try-with-resource closes the 376ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes // pipe with close() instead of closeWithError(). 377ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes try { 378ffb465f23d9549dd591e6aa62e9250523cb00233Elliott Hughes outputPipe.closeWithError(e.getMessage()); 3796d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } catch (IOException e2) { 3806d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers Log.e(TAG, "Failed to close the pipe after an error.", e2); 3816d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } 3825f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 3835f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } catch (OperationCanceledException e) { 384d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers // Cancelled gracefully. 3852cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier } catch (IOException e) { 386d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers Log.e(TAG, "Failed to close the output stream gracefully.", e); 3876d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers } finally { 3886d0b13e931fb73d5e964d14b0a7ecd1f3f5db547Ian Rogers IoUtils.closeQuietly(finalInputStream); 3895f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 3905f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 3912cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier }); 3922cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier 3930399dde18753aa9bd2bd0d7cf60beef154d164a4Ian Rogers return pipe[0]; 394d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 395d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers 3965f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes /** 3975f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * Opens a thumbnail of a file within an archive. 3985f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * 3995f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal)) 4005f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes */ 4015f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes public AssetFileDescriptor openDocumentThumbnail( 4025f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes String documentId, Point sizeHint, final CancellationSignal signal) 4034cba0d979a11f955e6ec3c0f1bf61478af7aa810Hiroshi Yamauchi throws FileNotFoundException { 4045f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter); 4055f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId, 4065f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes "Mismatching document ID. Expected: %s, actual: %s."); 4075f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive."); 4085f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"), 4094cd121ef0cb35fced70c7d9de378277be7a727d9Elliott Hughes "Thumbnails only supported for image/* MIME type."); 4104cd121ef0cb35fced70c7d9de378277be7a727d9Elliott Hughes 4112cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier // TODO: Extract thumbnails from EXIF. 4124cd121ef0cb35fced70c7d9de378277be7a727d9Elliott Hughes final ZipEntry entry = mZipFile.getEntry(parsedId.mPath); 4135f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes if (entry == null) { 414d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers throw new FileNotFoundException(); 415d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers } 4165f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 4175f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes return new AssetFileDescriptor( 418d9c4fc94fa618617f94e1de9af5f034549100753Ian Rogers openDocument(documentId, "r", signal), 0, entry.getSize(), null); 4191af6a1fa35ff7dc0a5c653f19dbc8a91c914aa42Elena Sayapina } 4205f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 4215f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes /** 4224cd121ef0cb35fced70c7d9de378277be7a727d9Elliott Hughes * Schedules a gracefully close of the archive after any opened files are closed. 423df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes * 424df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes * <p>This method does not block until shutdown. Once called, other methods should not be 425df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes * called. 426df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes */ 427df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes @Override 428df42c4815c30b9df15aacb88070c1e94f41d0226Elliott Hughes public void close() { 4295f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes mExecutor.execute(new Runnable() { 4305f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes @Override 4311af6a1fa35ff7dc0a5c653f19dbc8a91c914aa42Elena Sayapina public void run() { 4320aa50ce2fb75bfc2e815a0c33adf9b049561923bNicolas Geoffray IoUtils.closeQuietly(mZipFile); 433ef7d42fca18c16fbaf103822ad16f23246e2905dIan Rogers } 4345f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes }); 4355f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes mExecutor.shutdown(); 4365f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 4375f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 4385f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes private void addCursorRow(MatrixCursor cursor, ZipEntry entry) { 4395f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final MatrixCursor.RowBuilder row = cursor.newRow(); 4405f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final ParsedDocumentId parsedId = new ParsedDocumentId(mDocumentId, entry.getName()); 4415f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId(mIdDelimiter)); 4425f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 4435f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final File file = new File(entry.getName()); 4445f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 4455f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes row.add(Document.COLUMN_SIZE, entry.getSize()); 4465f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes 4475f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final String mimeType = getMimeTypeForEntry(entry); 448440e4ceb310349ee8eb569495bc04d3d7fbe71cbMathieu Chartier row.add(Document.COLUMN_MIME_TYPE, mimeType); 4490399dde18753aa9bd2bd0d7cf60beef154d164a4Ian Rogers 4505f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes final int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0; 4512cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier row.add(Document.COLUMN_FLAGS, flags); 452e401d146407d61eeb99f8d6176b2ac13c4df1e33Mathieu Chartier } 4532cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier 4540399dde18753aa9bd2bd0d7cf60beef154d164a4Ian Rogers private String getMimeTypeForEntry(ZipEntry entry) { 4550399dde18753aa9bd2bd0d7cf60beef154d164a4Ian Rogers if (entry.isDirectory()) { 4565f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes return Document.MIME_TYPE_DIR; 457b4e94fd380a84c755264e8668a16052442c7ec32Elliott Hughes } 45800f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers 459f1d666e1b48f8070ef1177fce156c08827f08eb8Mathieu Chartier final int lastDot = entry.getName().lastIndexOf('.'); 460f1d666e1b48f8070ef1177fce156c08827f08eb8Mathieu Chartier if (lastDot >= 0) { 461f1d666e1b48f8070ef1177fce156c08827f08eb8Mathieu Chartier final String extension = entry.getName().substring(lastDot + 1).toLowerCase(); 462f1d666e1b48f8070ef1177fce156c08827f08eb8Mathieu Chartier final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 463f1d666e1b48f8070ef1177fce156c08827f08eb8Mathieu Chartier if (mimeType != null) { 46400f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers return mimeType; 465dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers } 4665f79133a435ebcb20000370d56046fe01201dd80Elliott Hughes } 46700f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers 4682cebb24bfc3247d3e9be138a3350106737455918Mathieu Chartier return "application/octet-stream"; 46900f7d0eaa6bd93d33bf0c1429bf4ba0b3f28abacIan Rogers } 470dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers}; 471dd7624d2b9e599d57762d12031b10b89defc9807Ian Rogers