ArchivesProvider.java revision dbd6b8bd50ac660cb9eb83d0bd39712b65382797
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; 25dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.MatrixCursor.RowBuilder; 26dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.graphics.Point; 27dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.net.Uri; 28dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.CancellationSignal; 29dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 30dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract.Document; 31dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract; 32dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsProvider; 33dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.support.annotation.Nullable; 34dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.util.Log; 35dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.util.LruCache; 36dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 37dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy; 38dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.util.Preconditions; 39dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 40dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.Closeable; 41dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.File; 42dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.FileNotFoundException; 43dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.IOException; 44dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.HashMap; 45dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.Map; 46dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.Callable; 47dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.Lock; 48dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.ReadWriteLock; 49dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.ReentrantReadWriteLock; 50dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 51dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski/** 52dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Provides basic implementation for creating, extracting and accessing 53dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * files within archives exposed by a document provider. 54dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 55dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * <p>This class is thread safe. All methods can be called on any thread without 56dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * synchronization. 57dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 58dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskipublic class ArchivesProvider extends DocumentsProvider implements Closeable { 59dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static final String AUTHORITY = "com.android.documentsui.archives"; 60dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 61dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String TAG = "ArchivesProvider"; 62dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final int OPENED_ARCHIVES_CACHE_SIZE = 4; 63dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String[] ZIP_MIME_TYPES = { 64dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski "application/zip", "application/x-zip", "application/x-zip-compressed" 65dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }; 66dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 67dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @GuardedBy("mArchives") 68dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final LruCache<Uri, Loader> mArchives = 69dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski new LruCache<Uri, Loader>(OPENED_ARCHIVES_CACHE_SIZE) { 70dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 71dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void entryRemoved(boolean evicted, Uri key, 72dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader oldValue, Loader newValue) { 73dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.getWriteLock().lock(); 74dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 75dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.get().close(); 76dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } catch (FileNotFoundException e) { 77dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Log.e(TAG, "Failed to close an archive as it no longer exists."); 78dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 79dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski oldValue.getWriteLock().unlock(); 80dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 81dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 82dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }; 83dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 84dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 85dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean onCreate() { 86dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 87dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 88dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 89dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 90dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryRoots(String[] projection) { 91dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throw new UnsupportedOperationException(); 92dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 93dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 94dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 95dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryChildDocuments(String documentId, @Nullable String[] projection, 96dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Nullable String sortOrder) 97dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 98dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 99dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 100dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 101dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().queryChildDocuments(documentId, projection, sortOrder); 102dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 103dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 104dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 105dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 106dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 107dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 108dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public String getDocumentType(String documentId) throws FileNotFoundException { 109dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 110dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 111dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return Document.MIME_TYPE_DIR; 112dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 113dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 114dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 115dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 116dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 117dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().getDocumentType(documentId); 118dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 119dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 120dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 121dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 122dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 123dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 124dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean isChildDocument(String parentDocumentId, String documentId) { 125dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 126dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 127dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 128dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().isChildDocument(parentDocumentId, documentId); 129dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } catch (FileNotFoundException e) { 130dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throw new IllegalStateException(e); 131dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 132dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 133dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 134dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 135dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 136dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 137dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryDocument(String documentId, @Nullable String[] projection) 138dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 139dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 140dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 141dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // For the archive's root directory return hard-coded cursor, so clients know that 142dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // it's actually a directory and queryChildDocuments() can be called on it. 143dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // 144dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // TODO: Move this code to the Archive class, once opening archives is moved to 145dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // background. 146dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final MatrixCursor cursor = new MatrixCursor( 147dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski projection != null ? projection : Archive.DEFAULT_PROJECTION); 148dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final RowBuilder row = cursor.newRow(); 149dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski row.add(Document.COLUMN_DOCUMENT_ID, documentId); 150dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski row.add(Document.COLUMN_DISPLAY_NAME, "Archive"); // TODO: Fix. 151dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski row.add(Document.COLUMN_SIZE, 0); 152dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 153dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return cursor; 154dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 155dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 156dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 157dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 158dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 159dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().queryDocument(documentId, projection); 160dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 161dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 162dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 163dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 164dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 165dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 166dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public ParcelFileDescriptor openDocument( 167dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, String mode, final CancellationSignal signal) 168dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 169dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 170dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 171dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 172dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().openDocument(documentId, mode, signal); 173dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 174dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 175dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 176dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 177dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 178dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 179dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public AssetFileDescriptor openDocumentThumbnail( 180dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, Point sizeHint, final CancellationSignal signal) 181dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 182dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader = null; 183dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 184dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = obtainInstance(documentId); 185dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader.get().openDocumentThumbnail(documentId, sizeHint, signal); 186dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } finally { 187dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski releaseInstance(loader); 188dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 189dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 190dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 191dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 192dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Returns true if the passed mime type is supported by the helper. 193dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 194dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static boolean isSupportedArchiveType(String mimeType) { 195dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski for (final String zipMimeType : ZIP_MIME_TYPES) { 196dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (zipMimeType.equals(mimeType)) { 197dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 198dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 199dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 200dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return false; 201dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 202dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 203dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static Uri buildUriForArchive(Uri archiveUri) { 204dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return DocumentsContract.buildDocumentUri( 205dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski AUTHORITY, new ArchiveId(archiveUri, "/").toDocumentId()); 206dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 207dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 208dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 209dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Closes the helper and disposes all existing archives. It will block until all ongoing 210dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * operations on each opened archive are finished. 211dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 212dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 213dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // TODO: Wire close() to call(). 214dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void close() { 215dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 216dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.evictAll(); 217dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 218dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 219dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 220dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private Loader obtainInstance(String documentId) throws FileNotFoundException { 221dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader loader; 222dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 223dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader = getInstanceUncheckedLocked(documentId); 224dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader.getReadLock().lock(); 225dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 226dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader; 227dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 228dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 229dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private void releaseInstance(@Nullable Loader loader) { 230dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (loader != null) { 231dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski loader.getReadLock().unlock(); 232dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 233dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 234dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 235dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private Loader getInstanceUncheckedLocked(String documentId) 236dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 237dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId id = ArchiveId.fromDocumentId(documentId); 238dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (mArchives.get(id.mArchiveUri) != null) { 239dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mArchives.get(id.mArchiveUri); 240dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 241dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 242dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Cursor cursor = getContext().getContentResolver().query( 243dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski id.mArchiveUri, new String[] { Document.COLUMN_MIME_TYPE }, null, null, null); 244dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski cursor.moveToFirst(); 245dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final String mimeType = cursor.getString(cursor.getColumnIndex( 246dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Document.COLUMN_MIME_TYPE)); 247dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Preconditions.checkArgument(isSupportedArchiveType(mimeType)); 248dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Uri notificationUri = cursor.getNotificationUri(); 249dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Loader loader = new Loader(getContext(), id.mArchiveUri, notificationUri); 250dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 251dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski // Remove the instance from mArchives collection once the archive file changes. 252dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (notificationUri != null) { 253dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final LruCache<Uri, Loader> finalArchives = mArchives; 254dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski getContext().getContentResolver().registerContentObserver(notificationUri, 255dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski false, 256dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski new ContentObserver(null) { 257dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 258dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public void onChange(boolean selfChange, Uri uri) { 259dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 260dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final Loader currentLoader = mArchives.get(id.mArchiveUri); 261dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (currentLoader == loader) { 262dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.remove(id.mArchiveUri); 263dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 264dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 265dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 266dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }); 267dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 268dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 269dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchives.put(id.mArchiveUri, loader); 270dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return loader; 271dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 272dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 273dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 274dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Loads an instance of Archive lazily. 275dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 276dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final class Loader { 277dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final Context mContext; 278dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final Uri mArchiveUri; 279dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final Uri mNotificationUri; 280dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); 281dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private Archive mArchive = null; 282dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 283dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Loader(Context context, Uri archiveUri, Uri notificationUri) { 284dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski this.mContext = context; 285dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski this.mArchiveUri = archiveUri; 286dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski this.mNotificationUri = notificationUri; 287dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 288dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 289dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized Archive get() throws FileNotFoundException { 290dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (mArchive != null) { 291dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mArchive; 292dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 293dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 294dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski try { 295dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchive = Archive.createForParcelFileDescriptor( 296dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mContext, 297dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mContext.getContentResolver().openFileDescriptor( 298dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchiveUri, "r", null /* signal */), 299dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski mArchiveUri, mNotificationUri); 300dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } catch (IOException e) { 301dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throw new IllegalStateException(e); 302dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 303dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 304dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mArchive; 305dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 306dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 307dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Lock getReadLock() { 308dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mLock.readLock(); 309dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 310dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 311dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski Lock getWriteLock() { 312dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return mLock.writeLock(); 313dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 314dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 315dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski} 316