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