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