ArchivesProvider.java revision 00c0ce8fbb0e04d0fc3d05f33cd24225be6d328f
1dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski/* 2dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Copyright (C) 2015 The Android Open Source Project 3dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 4dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Licensed under the Apache License, Version 2.0 (the "License"); 5dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * you may not use this file except in compliance with the License. 6dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * You may obtain a copy of the License at 7dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 8dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * http://www.apache.org/licenses/LICENSE-2.0 9dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 10dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Unless required by applicable law or agreed to in writing, software 11dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * distributed under the License is distributed on an "AS IS" BASIS, 12dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * See the License for the specific language governing permissions and 14dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * limitations under the License. 15dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 16dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 17dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskipackage com.android.documentsui.archives; 18dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 19dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.Context; 20dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.res.AssetFileDescriptor; 21dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.res.Configuration; 22dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.ContentObserver; 23dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.Cursor; 24dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.MatrixCursor.RowBuilder; 255a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewskiimport android.database.MatrixCursor; 26dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.graphics.Point; 27dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.net.Uri; 285a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewskiimport android.os.Bundle; 29dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.CancellationSignal; 30dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 31dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract.Document; 32dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract; 33dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsProvider; 34dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.support.annotation.Nullable; 35dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.util.Log; 36dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.util.LruCache; 37dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 3855b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewskiimport com.android.documentsui.R; 39dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy; 40dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.util.Preconditions; 41dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 42dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.Closeable; 43dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.File; 44dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.FileNotFoundException; 4500c0ce8fbb0e04d0fc3d05f33cd24225be6d328fTomasz Mikolajewskiimport java.io.IOException; 46dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.HashMap; 47dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.Map; 48dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.Lock; 49dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 50dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski/** 51dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Provides basic implementation for creating, extracting and accessing 52dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * files within archives exposed by a document provider. 53dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 54dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * <p>This class is thread safe. All methods can be called on any thread without 55dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * synchronization. 56dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 57dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskipublic class ArchivesProvider extends DocumentsProvider implements Closeable { 58dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static final String AUTHORITY = "com.android.documentsui.archives"; 59dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 60dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String TAG = "ArchivesProvider"; 61dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final int OPENED_ARCHIVES_CACHE_SIZE = 4; 62dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String[] ZIP_MIME_TYPES = { 63dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski "application/zip", "application/x-zip", "application/x-zip-compressed" 64dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }; 65dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 66dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @GuardedBy("mArchives") 67dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final LruCache<Uri, Loader> mArchives = 68dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski new LruCache<Uri, Loader>(OPENED_ARCHIVES_CACHE_SIZE) { 69dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 70dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void entryRemoved(boolean evicted, Uri key, 71dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader oldValue, Loader newValue) { 72dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.getWriteLock().lock(); 73dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 74dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.get().close(); 7500c0ce8fbb0e04d0fc3d05f33cd24225be6d328fTomasz Mikolajewski } catch (IOException e) { 7600c0ce8fbb0e04d0fc3d05f33cd24225be6d328fTomasz Mikolajewski Log.e(TAG, "Closing archive failed.", e); 7700c0ce8fbb0e04d0fc3d05f33cd24225be6d328fTomasz Mikolajewski }finally { 78dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.getWriteLock().unlock(); 79dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 80dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 81dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }; 82dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 83dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 84dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean onCreate() { 85dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 86dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 87dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 88dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 89dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryRoots(String[] projection) { 90dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throw new UnsupportedOperationException(); 91dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 92dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 93dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 94dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryChildDocuments(String documentId, @Nullable String[] projection, 95dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Nullable String sortOrder) 96dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 975a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 98dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 99dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 100dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 10155b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski final int status = loader.getStatus(); 10255b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski // If already loaded, then forward the request to the archive. 10355b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski if (status == Loader.STATUS_OPENED) { 10455b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski return loader.get().queryChildDocuments(documentId, projection, sortOrder); 10555b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski } 10655b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 10755b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski final MatrixCursor cursor = new MatrixCursor( 10855b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski projection != null ? projection : Archive.DEFAULT_PROJECTION); 10955b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski final Bundle bundle = new Bundle(); 11055b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 11155b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski switch (status) { 11255b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski case Loader.STATUS_OPENING: 11355b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true); 11455b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski break; 11555b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 11655b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski case Loader.STATUS_FAILED: 11755b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski // Return an empty cursor with EXTRA_LOADING, which shows spinner 11855b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski // in DocumentsUI. Once the archive is loaded, the notification will 11955b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski // be sent, and the directory reloaded. 12055b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski bundle.putString(DocumentsContract.EXTRA_ERROR, 12155b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski getContext().getString(R.string.archive_loading_failed)); 12255b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski break; 1235a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski } 12455b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 12555b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski cursor.setExtras(bundle); 12655b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski cursor.setNotificationUri(getContext().getContentResolver(), 127c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski buildUriForArchive(archiveId.mArchiveUri, archiveId.mAccessMode)); 12855b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski return cursor; 129dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 130dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 131dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 132dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 133dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 134dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 135dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public String getDocumentType(String documentId) throws FileNotFoundException { 136dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 137dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 138dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return Document.MIME_TYPE_DIR; 139dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 140dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 141dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 142dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 143dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 144dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().getDocumentType(documentId); 145dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 146dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 147dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 148dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 149dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 150dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 151dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean isChildDocument(String parentDocumentId, String documentId) { 152dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 153dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 154dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 155dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().isChildDocument(parentDocumentId, documentId); 156dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } catch (FileNotFoundException e) { 157dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throw new IllegalStateException(e); 158dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 159dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 160dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 161dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 162dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 163dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 164dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryDocument(String documentId, @Nullable String[] projection) 165dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 166dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 167dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 1681794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski try (final Cursor archiveCursor = getContext().getContentResolver().query( 1691794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski archiveId.mArchiveUri, 1701794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski new String[] { Document.COLUMN_DISPLAY_NAME }, 1711794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski null, null, null, null)) { 172a352711544911b294ea511c757320a50a2835091Tomasz Mikolajewski if (archiveCursor == null || !archiveCursor.moveToFirst()) { 1731794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski throw new FileNotFoundException( 1741794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski "Cannot resolve display name of the archive."); 1751794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski } 1761794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final String displayName = archiveCursor.getString( 1771794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski archiveCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)); 1781794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski 1791794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final MatrixCursor cursor = new MatrixCursor( 1801794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski projection != null ? projection : Archive.DEFAULT_PROJECTION); 1811794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final RowBuilder row = cursor.newRow(); 1821794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_DOCUMENT_ID, documentId); 1831794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_DISPLAY_NAME, displayName); 1841794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_SIZE, 0); 1851794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 1861794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski return cursor; 1871794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski } 188dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 189dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 190dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 191dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 192dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 193dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().queryDocument(documentId, projection); 194dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 195dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 196dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 197dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 198dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 199dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 200dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public ParcelFileDescriptor openDocument( 201dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, String mode, final CancellationSignal signal) 202dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 203dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 204dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 205dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 206dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().openDocument(documentId, mode, signal); 207dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 208dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 209dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 210dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 211dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 212dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 213dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public AssetFileDescriptor openDocumentThumbnail( 214dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, Point sizeHint, final CancellationSignal signal) 215dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 216dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 217dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 218dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 219dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().openDocumentThumbnail(documentId, sizeHint, signal); 220dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 221dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 222dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 223dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 224dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 225dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 226dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Returns true if the passed mime type is supported by the helper. 227dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 228dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static boolean isSupportedArchiveType(String mimeType) { 229dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski for (final String zipMimeType : ZIP_MIME_TYPES) { 230dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (zipMimeType.equals(mimeType)) { 231dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 232dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 233dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 234dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return false; 235dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 236dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 237c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski /** 238c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * Creates a Uri for accessing an archive with the specified access mode. 239c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * 240c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * @see ParcelFileDescriptor#MODE_READ 241c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * @see ParcelFileDescriptor#MODE_WRITE 242c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski */ 243c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski public static Uri buildUriForArchive(Uri archiveUri, int accessMode) { 244c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski return DocumentsContract.buildDocumentUri(AUTHORITY, 245c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski new ArchiveId(archiveUri, accessMode, "/").toDocumentId()); 246dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 247dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 248dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 249dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Closes the helper and disposes all existing archives. It will block until all ongoing 250dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * operations on each opened archive are finished. 251dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 252dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 253dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // TODO: Wire close() to call(). 254dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void close() { 255dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 256dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.evictAll(); 257dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 258dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 259dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 260dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private Loader obtainInstance(String documentId) throws FileNotFoundException { 261dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader; 262dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 263dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = getInstanceUncheckedLocked(documentId); 264dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader.getReadLock().lock(); 265dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 266dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader; 267dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 268dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 269dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private void releaseInstance(@Nullable Loader loader) { 270dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (loader != null) { 271dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader.getReadLock().unlock(); 272dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 273dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 274dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 275c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski private Loader getInstanceUncheckedLocked(String documentId) throws FileNotFoundException { 276dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId id = ArchiveId.fromDocumentId(documentId); 277dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (mArchives.get(id.mArchiveUri) != null) { 278dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mArchives.get(id.mArchiveUri); 279dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 280dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 281dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Cursor cursor = getContext().getContentResolver().query( 282dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski id.mArchiveUri, new String[] { Document.COLUMN_MIME_TYPE }, null, null, null); 28355b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski if (cursor == null || cursor.getCount() == 0) { 28455b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski throw new FileNotFoundException("File not found." + id.mArchiveUri); 28555b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski } 28655b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 287dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski cursor.moveToFirst(); 288dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final String mimeType = cursor.getString(cursor.getColumnIndex( 289dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Document.COLUMN_MIME_TYPE)); 290dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Preconditions.checkArgument(isSupportedArchiveType(mimeType)); 291dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Uri notificationUri = cursor.getNotificationUri(); 292c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski final Loader loader = new Loader(getContext(), id.mArchiveUri, id.mAccessMode, 293c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski notificationUri); 294dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 295dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // Remove the instance from mArchives collection once the archive file changes. 296dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (notificationUri != null) { 297dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final LruCache<Uri, Loader> finalArchives = mArchives; 298dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski getContext().getContentResolver().registerContentObserver(notificationUri, 299dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski false, 300dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski new ContentObserver(null) { 301dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 302dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void onChange(boolean selfChange, Uri uri) { 303dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 304dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Loader currentLoader = mArchives.get(id.mArchiveUri); 305dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (currentLoader == loader) { 306dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.remove(id.mArchiveUri); 307dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 308dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 309dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 310dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }); 311dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 312dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 313dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.put(id.mArchiveUri, loader); 314dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader; 315dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 316dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski} 317