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