ArchivesProvider.java revision 5a9d100fe3d5b596d94a387e2dc370b84624062a
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
38dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy;
39dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport com.android.internal.util.Preconditions;
40dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
41dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.Closeable;
42dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.File;
43dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.io.FileNotFoundException;
44dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.HashMap;
45dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.Map;
46dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskiimport java.util.concurrent.locks.Lock;
47dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
48dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski/**
49dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * Provides basic implementation for creating, extracting and accessing
50dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * files within archives exposed by a document provider.
51dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski *
52dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * <p>This class is thread safe. All methods can be called on any thread without
53dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski * synchronization.
54dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski */
55dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewskipublic class ArchivesProvider extends DocumentsProvider implements Closeable {
56dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public static final String AUTHORITY = "com.android.documentsui.archives";
57dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
58dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private static final String TAG = "ArchivesProvider";
59dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private static final int OPENED_ARCHIVES_CACHE_SIZE = 4;
60dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private static final String[] ZIP_MIME_TYPES = {
61dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            "application/zip", "application/x-zip", "application/x-zip-compressed"
62dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    };
63dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
64dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @GuardedBy("mArchives")
65dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private final LruCache<Uri, Loader> mArchives =
66dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            new LruCache<Uri, Loader>(OPENED_ARCHIVES_CACHE_SIZE) {
67dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                @Override
68dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                public void entryRemoved(boolean evicted, Uri key,
69dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        Loader oldValue, Loader newValue) {
70dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    oldValue.getWriteLock().lock();
71dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    try {
72dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        oldValue.get().close();
73dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    } catch (FileNotFoundException e) {
74dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        Log.e(TAG, "Failed to close an archive as it no longer exists.");
75dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    } finally {
76dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        oldValue.getWriteLock().unlock();
77dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    }
78dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                }
79dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            };
80dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
81dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
82dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public boolean onCreate() {
83dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        return true;
84dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
85dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
86dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
87dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public Cursor queryRoots(String[] projection) {
88dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        throw new UnsupportedOperationException();
89dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
90dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
91dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
92dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
93dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            @Nullable String sortOrder)
94dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throws FileNotFoundException {
955a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
96dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
97dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
98dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
995a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski            if (loader.mArchive == null) {
1005a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                final MatrixCursor cursor = new MatrixCursor(
1015a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                        projection != null ? projection : Archive.DEFAULT_PROJECTION);
1025a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                // Return an empty cursor with EXTRA_LOADING, which shows spinner
1035a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                // in DocumentsUI. Once the archive is loaded, the notification will
1045a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                // be sent, and the directory reloaded.
1055a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                final Bundle bundle = new Bundle();
1065a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
1075a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                cursor.setExtras(bundle);
1085a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                cursor.setNotificationUri(getContext().getContentResolver(),
1095a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                        buildUriForArchive(archiveId.mArchiveUri));
1105a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski                return cursor;
1115a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski            }
112dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().queryChildDocuments(documentId, projection, sortOrder);
113dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
114dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
115dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
116dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
117dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
118dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
119dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public String getDocumentType(String documentId) throws FileNotFoundException {
120dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
121dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        if (archiveId.mPath.equals("/")) {
122dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return Document.MIME_TYPE_DIR;
123dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
124dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
125dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
126dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
127dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
128dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().getDocumentType(documentId);
129dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
130dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
131dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
132dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
133dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
134dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
135dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public boolean isChildDocument(String parentDocumentId, String documentId) {
136dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
137dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
138dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
139dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().isChildDocument(parentDocumentId, documentId);
140dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } catch (FileNotFoundException e) {
141dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throw new IllegalStateException(e);
142dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
143dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
144dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
145dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
146dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
147dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
148dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public Cursor queryDocument(String documentId, @Nullable String[] projection)
149dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throws FileNotFoundException {
150dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
151dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        if (archiveId.mPath.equals("/")) {
152dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            final MatrixCursor cursor = new MatrixCursor(
153dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    projection != null ? projection : Archive.DEFAULT_PROJECTION);
154dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            final RowBuilder row = cursor.newRow();
155dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            row.add(Document.COLUMN_DOCUMENT_ID, documentId);
1565a9d100fe3d5b596d94a387e2dc370b84624062aTomasz Mikolajewski            row.add(Document.COLUMN_DISPLAY_NAME, "Archive");  // TODO: Translate.
157dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            row.add(Document.COLUMN_SIZE, 0);
158dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
159dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return cursor;
160dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
161dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
162dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
163dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
164dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
165dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().queryDocument(documentId, projection);
166dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
167dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
168dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
169dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
170dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
171dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
172dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public ParcelFileDescriptor openDocument(
173dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            String documentId, String mode, final CancellationSignal signal)
174dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throws FileNotFoundException {
175dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
176dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
177dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
178dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().openDocument(documentId, mode, signal);
179dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
180dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
181dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
182dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
183dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
184dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
185dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public AssetFileDescriptor openDocumentThumbnail(
186dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            String documentId, Point sizeHint, final CancellationSignal signal)
187dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throws FileNotFoundException {
188dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader = null;
189dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        try {
190dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = obtainInstance(documentId);
191dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
192dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        } finally {
193dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            releaseInstance(loader);
194dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
195dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
196dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
197dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    /**
198dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski     * Returns true if the passed mime type is supported by the helper.
199dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski     */
200dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public static boolean isSupportedArchiveType(String mimeType) {
201dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        for (final String zipMimeType : ZIP_MIME_TYPES) {
202dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            if (zipMimeType.equals(mimeType)) {
203dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                return true;
204dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            }
205dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
206dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        return false;
207dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
208dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
209dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public static Uri buildUriForArchive(Uri archiveUri) {
210dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        return DocumentsContract.buildDocumentUri(
211dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                AUTHORITY, new ArchiveId(archiveUri, "/").toDocumentId());
212dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
213dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
214dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    /**
215dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski     * Closes the helper and disposes all existing archives. It will block until all ongoing
216dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski     * operations on each opened archive are finished.
217dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski     */
218dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    @Override
219dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    // TODO: Wire close() to call().
220dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    public void close() {
221dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        synchronized (mArchives) {
222dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            mArchives.evictAll();
223dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
224dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
225dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
226dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private Loader obtainInstance(String documentId) throws FileNotFoundException {
227dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Loader loader;
228dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        synchronized (mArchives) {
229dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader = getInstanceUncheckedLocked(documentId);
230dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader.getReadLock().lock();
231dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
232dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        return loader;
233dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
234dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
235dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private void releaseInstance(@Nullable Loader loader) {
236dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        if (loader != null) {
237dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            loader.getReadLock().unlock();
238dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
239dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
240dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
241dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    private Loader getInstanceUncheckedLocked(String documentId)
242dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            throws FileNotFoundException {
243dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final ArchiveId id = ArchiveId.fromDocumentId(documentId);
244dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        if (mArchives.get(id.mArchiveUri) != null) {
245dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            return mArchives.get(id.mArchiveUri);
246dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
247dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
248dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final Cursor cursor = getContext().getContentResolver().query(
249dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                id.mArchiveUri, new String[] { Document.COLUMN_MIME_TYPE }, null, null, null);
250dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        cursor.moveToFirst();
251dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final String mimeType = cursor.getString(cursor.getColumnIndex(
252dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                Document.COLUMN_MIME_TYPE));
253dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        Preconditions.checkArgument(isSupportedArchiveType(mimeType));
254dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final Uri notificationUri = cursor.getNotificationUri();
255dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        final Loader loader = new Loader(getContext(), id.mArchiveUri, notificationUri);
256dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
257dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        // Remove the instance from mArchives collection once the archive file changes.
258dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        if (notificationUri != null) {
259dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            final LruCache<Uri, Loader> finalArchives = mArchives;
260dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski            getContext().getContentResolver().registerContentObserver(notificationUri,
261dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    false,
262dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    new ContentObserver(null) {
263dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        @Override
264dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        public void onChange(boolean selfChange, Uri uri) {
265dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                            synchronized (mArchives) {
266dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                                final Loader currentLoader = mArchives.get(id.mArchiveUri);
267dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                                if (currentLoader == loader) {
268dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                                    mArchives.remove(id.mArchiveUri);
269dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                                }
270dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                            }
271dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                        }
272dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski                    });
273dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        }
274dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski
275dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        mArchives.put(id.mArchiveUri, loader);
276dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski        return loader;
277dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski    }
278dbd6b8bd50ac660cb9eb83d0bd39712b65382797Tomasz Mikolajewski}
279