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 19ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewskiimport android.content.ContentProviderClient; 20ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewskiimport android.content.ContentResolver; 21dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.Context; 22dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.res.AssetFileDescriptor; 23dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.content.res.Configuration; 24dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.ContentObserver; 25dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.Cursor; 26dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.database.MatrixCursor.RowBuilder; 275a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewskiimport android.database.MatrixCursor; 28dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.graphics.Point; 29dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.net.Uri; 305a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewskiimport android.os.Bundle; 31dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.CancellationSignal; 32dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 33dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract.Document; 34f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewskiimport android.provider.DocumentsContract.Root; 35dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsContract; 36dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.provider.DocumentsProvider; 37dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.support.annotation.Nullable; 38dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport android.util.Log; 39dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 4055b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewskiimport com.android.documentsui.R; 41dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy; 42dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.util.Preconditions; 43dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 44dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.Closeable; 45dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.File; 46dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.FileNotFoundException; 4700c0ce8fbb0e04d0fc3d05f33cd24225be6d328fTomasz Mikolajewskiimport java.io.IOException; 48dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.HashMap; 49dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.Map; 505886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewskiimport java.util.Objects; 51dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.Lock; 52dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 53dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski/** 54dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Provides basic implementation for creating, extracting and accessing 55dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * files within archives exposed by a document provider. 56dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * 57dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * <p>This class is thread safe. All methods can be called on any thread without 58dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * synchronization. 59dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 60b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewskipublic class ArchivesProvider extends DocumentsProvider { 61dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static final String AUTHORITY = "com.android.documentsui.archives"; 62dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 63f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewski private static final String[] DEFAULT_ROOTS_PROJECTION = new String[] { 64f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewski Root.COLUMN_ROOT_ID, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_TITLE, Root.COLUMN_FLAGS, 65f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewski Root.COLUMN_ICON }; 66dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String TAG = "ArchivesProvider"; 67b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private static final String METHOD_ACQUIRE_ARCHIVE = "acquireArchive"; 68b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private static final String METHOD_RELEASE_ARCHIVE = "releaseArchive"; 69dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski private static final String[] ZIP_MIME_TYPES = { 70dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski "application/zip", "application/x-zip", "application/x-zip-compressed" 71dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski }; 72dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 73dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @GuardedBy("mArchives") 74b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private final Map<Key, Loader> mArchives = new HashMap<Key, Loader>(); 75dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 76dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 77ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski public Bundle call(String method, String arg, Bundle extras) { 78b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (METHOD_ACQUIRE_ARCHIVE.equals(method)) { 79b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski acquireArchive(arg); 80b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return null; 81b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } 82b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski 83b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (METHOD_RELEASE_ARCHIVE.equals(method)) { 84b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski releaseArchive(arg); 85ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski return null; 86ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 87ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 88ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski return super.call(method, arg, extras); 89ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 90ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 91ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski @Override 92dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean onCreate() { 93dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 94dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 95dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 96dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 97dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryRoots(String[] projection) { 98f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewski // No roots provided. 99f78d2052ea98f357e8763b8080cc215b41cd1198Tomasz Mikolajewski return new MatrixCursor(projection != null ? projection : DEFAULT_ROOTS_PROJECTION); 100dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 101dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 102dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 103dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryChildDocuments(String documentId, @Nullable String[] projection, 104dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Nullable String sortOrder) 105dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 1065a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 107b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 108b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final int status = loader.getStatus(); 109b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski // If already loaded, then forward the request to the archive. 110b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (status == Loader.STATUS_OPENED) { 111b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().queryChildDocuments(documentId, projection, sortOrder); 112b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } 11355b6b48c07fce872ad651267ce699f99b4b16e3eTomasz Mikolajewski 114b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final MatrixCursor cursor = new MatrixCursor( 115b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski projection != null ? projection : Archive.DEFAULT_PROJECTION); 116b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Bundle bundle = new Bundle(); 117b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski 118b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski switch (status) { 119b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski case Loader.STATUS_OPENING: 120b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true); 121b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski break; 122b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski 123b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski case Loader.STATUS_FAILED: 124b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski // Return an empty cursor with EXTRA_LOADING, which shows spinner 125b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski // in DocumentsUI. Once the archive is loaded, the notification will 126b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski // be sent, and the directory reloaded. 127b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski bundle.putString(DocumentsContract.EXTRA_ERROR, 128b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski getContext().getString(R.string.archive_loading_failed)); 129b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski break; 130dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 131b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski 132b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski cursor.setExtras(bundle); 133b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski cursor.setNotificationUri(getContext().getContentResolver(), 134b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski buildUriForArchive(archiveId.mArchiveUri, archiveId.mAccessMode)); 135b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return cursor; 136dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 137dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 138dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 139dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public String getDocumentType(String documentId) throws FileNotFoundException { 140dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 141dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 142dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return Document.MIME_TYPE_DIR; 143dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 144dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 145b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 146b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().getDocumentType(documentId); 147dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 148dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 149dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 150dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public boolean isChildDocument(String parentDocumentId, String documentId) { 151b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 152b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().isChildDocument(parentDocumentId, documentId); 153dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 154dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 155dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 156dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public Cursor queryDocument(String documentId, @Nullable String[] projection) 157dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 158dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 159dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (archiveId.mPath.equals("/")) { 1601794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski try (final Cursor archiveCursor = getContext().getContentResolver().query( 1611794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski archiveId.mArchiveUri, 1621794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski new String[] { Document.COLUMN_DISPLAY_NAME }, 1631794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski null, null, null, null)) { 164a352711544911b294ea511c757320a50a2835091Tomasz Mikolajewski if (archiveCursor == null || !archiveCursor.moveToFirst()) { 1651794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski throw new FileNotFoundException( 1661794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski "Cannot resolve display name of the archive."); 1671794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski } 1681794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final String displayName = archiveCursor.getString( 1691794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski archiveCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)); 1701794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski 1711794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final MatrixCursor cursor = new MatrixCursor( 1721794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski projection != null ? projection : Archive.DEFAULT_PROJECTION); 1731794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski final RowBuilder row = cursor.newRow(); 1741794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_DOCUMENT_ID, documentId); 1751794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_DISPLAY_NAME, displayName); 1761794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_SIZE, 0); 1771794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 1781794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski return cursor; 1791794d444ed253a65ec095b740dc8276913784097Tomasz Mikolajewski } 180dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 181dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 182b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 183b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().queryDocument(documentId, projection); 184dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 185dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 186dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 187ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski public String createDocument( 188ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski String parentDocumentId, String mimeType, String displayName) 189ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski throws FileNotFoundException { 190b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(parentDocumentId); 191b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().createDocument(parentDocumentId, mimeType, displayName); 192ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 193ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 194ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski @Override 195dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public ParcelFileDescriptor openDocument( 196dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, String mode, final CancellationSignal signal) 197dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 198b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 199b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().openDocument(documentId, mode, signal); 200dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 201dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 202dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski @Override 203dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public AssetFileDescriptor openDocumentThumbnail( 204dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski String documentId, Point sizeHint, final CancellationSignal signal) 205dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski throws FileNotFoundException { 206b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = getLoaderOrThrow(documentId); 207b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader.get().openDocumentThumbnail(documentId, sizeHint, signal); 208dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 209dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 210dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 211dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Returns true if the passed mime type is supported by the helper. 212dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 213dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski public static boolean isSupportedArchiveType(String mimeType) { 214dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski for (final String zipMimeType : ZIP_MIME_TYPES) { 215dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski if (zipMimeType.equals(mimeType)) { 216dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return true; 217dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 218dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 219dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski return false; 220dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 221dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 222c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski /** 223c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * Creates a Uri for accessing an archive with the specified access mode. 224c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * 225c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * @see ParcelFileDescriptor#MODE_READ 226c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski * @see ParcelFileDescriptor#MODE_WRITE 227c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski */ 228ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski public static Uri buildUriForArchive(Uri externalUri, int accessMode) { 229c8fb30f1b909e2c6178ac7ad9980b48ed6b6d2c8Tomasz Mikolajewski return DocumentsContract.buildDocumentUri(AUTHORITY, 230ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski new ArchiveId(externalUri, accessMode, "/").toDocumentId()); 231ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 232ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 233ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski /** 234b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski * Acquires an archive. 235ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski */ 236b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski public static void acquireArchive(ContentProviderClient client, Uri archiveUri) { 237ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(), 238ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski "Mismatching authority. Expected: %s, actual: %s."); 239ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski final String documentId = DocumentsContract.getDocumentId(archiveUri); 240ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 241b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski try { 242b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski client.call(METHOD_ACQUIRE_ARCHIVE, documentId, null); 243ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } catch (Exception e) { 244b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski Log.w(TAG, "Failed to acquire archive.", e); 245ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 246ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 247ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski 248ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski /** 249b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski * Releases an archive. 250ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski */ 251b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski public static void releaseArchive(ContentProviderClient client, Uri archiveUri) { 252b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(), 253b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski "Mismatching authority. Expected: %s, actual: %s."); 254b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final String documentId = DocumentsContract.getDocumentId(archiveUri); 255b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski 256b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski try { 257b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski client.call(METHOD_RELEASE_ARCHIVE, documentId, null); 258b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } catch (Exception e) { 259b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski Log.w(TAG, "Failed to release archive.", e); 260ed11f1c0d5d8d295faf6e35cec8c11d325dc6433Tomasz Mikolajewski } 261dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 262dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 263dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski /** 264b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski * The archive won't close until all clients release it. 265dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */ 266b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private void acquireArchive(String documentId) { 267b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 268dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 269b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Key key = Key.fromArchiveId(archiveId); 270b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski Loader loader = mArchives.get(key); 271b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (loader == null) { 272b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski // TODO: Pass parent Uri so the loader can acquire the parent's notification Uri. 273b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski loader = new Loader(getContext(), archiveId.mArchiveUri, archiveId.mAccessMode, 274b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski null); 275b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski mArchives.put(key, loader); 276b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } 277b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski loader.acquire(); 278b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski mArchives.put(key, loader); 279dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 280dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 281dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 282b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski /** 283b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski * If all clients release the archive, then it will be closed. 284b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski */ 285b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private void releaseArchive(String documentId) { 286b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId); 287b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Key key = Key.fromArchiveId(archiveId); 288dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski synchronized (mArchives) { 289b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = mArchives.get(key); 290b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski loader.release(); 291b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final int status = loader.getStatus(); 292b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (status == Loader.STATUS_CLOSED || status == Loader.STATUS_CLOSING) { 293b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski mArchives.remove(key); 294b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } 295dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 296dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 297dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski 298b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski private Loader getLoaderOrThrow(String documentId) { 299dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski final ArchiveId id = ArchiveId.fromDocumentId(documentId); 3005886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski final Key key = Key.fromArchiveId(id); 301b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski synchronized (mArchives) { 302b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski final Loader loader = mArchives.get(key); 303b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski if (loader == null) { 304b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski throw new IllegalStateException("Archive not acquired."); 305b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski } 306b19061c6827780f31e4247ff7eaf47e153098e55Tomasz Mikolajewski return loader; 307dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 308dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski } 3095886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski 3105886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski private static class Key { 3115886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski Uri archiveUri; 3125886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski int accessMode; 3135886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski 3145886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski public Key(Uri archiveUri, int accessMode) { 3155886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski this.archiveUri = archiveUri; 3165886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski this.accessMode = accessMode; 3175886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3185886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski 3195886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski public static Key fromArchiveId(ArchiveId id) { 3205886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski return new Key(id.mArchiveUri, id.mAccessMode); 3215886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3225886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski 3235886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski @Override 3245886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski public boolean equals(Object other) { 3255886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski if (other == null) { 3265886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski return false; 3275886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3285886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski if (!(other instanceof Key)) { 3295886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski return false; 3305886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3315886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski return archiveUri.equals(((Key) other).archiveUri) && 3325886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski accessMode == ((Key) other).accessMode; 3335886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3345886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski 3355886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski @Override 3365886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski public int hashCode() { 3375886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski return Objects.hash(archiveUri, accessMode); 3385886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 3395886b749887ff8d8f80b32372d4ef76af67c0400Tomasz Mikolajewski } 340dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski} 341