ReadableArchive.java revision a903c2cdad65d0fe163a4152faa6bd05e2888b22
1d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski/* 2d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * Copyright (C) 2017 The Android Open Source Project 3d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 4d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * Licensed under the Apache License, Version 2.0 (the "License"); 5d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * you may not use this file except in compliance with the License. 6d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * You may obtain a copy of the License at 7d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 8d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * http://www.apache.org/licenses/LICENSE-2.0 9d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 10d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * Unless required by applicable law or agreed to in writing, software 11d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * distributed under the License is distributed on an "AS IS" BASIS, 12d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * See the License for the specific language governing permissions and 14d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * limitations under the License. 15d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski */ 16d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 17d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskipackage com.android.documentsui.archives; 18d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 19d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.content.Context; 20d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.content.res.AssetFileDescriptor; 21d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.graphics.Point; 22d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.media.ExifInterface; 23d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.net.Uri; 24d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.os.Bundle; 25d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.os.CancellationSignal; 26d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.os.OperationCanceledException; 27d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.os.ParcelFileDescriptor; 28d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.provider.DocumentsContract; 29d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.support.annotation.Nullable; 30d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.util.Log; 31d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport android.util.jar.StrictJarFile; 32d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 3379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewskiimport com.android.internal.annotations.GuardedBy; 34d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport com.android.internal.util.Preconditions; 35d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 36d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport libcore.io.IoUtils; 37d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 38d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.File; 39d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.FileDescriptor; 40d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.FileNotFoundException; 41d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.FileOutputStream; 42d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.IOException; 43d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.io.InputStream; 44d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.util.ArrayList; 4579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewskiimport java.util.HashSet; 46d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.util.Iterator; 47d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.util.List; 4879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewskiimport java.util.Set; 49d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.util.Stack; 503b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewskiimport java.util.concurrent.LinkedBlockingQueue; 5179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewskiimport java.util.concurrent.RejectedExecutionException; 523b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewskiimport java.util.concurrent.ThreadPoolExecutor; 533b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewskiimport java.util.concurrent.TimeUnit; 54d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskiimport java.util.zip.ZipEntry; 55d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 56d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski/** 57d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * Provides basic implementation for extracting and accessing 58d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * files within archives exposed by a document provider. 59d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 60d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * <p>This class is thread safe. 61d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski */ 62d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewskipublic class ReadableArchive extends Archive { 637bb3bdce2d2eb8e279073420151e48a4a0fb60c7Tomasz Mikolajewski private static final String TAG = "ReadableArchive"; 64d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 6579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski @GuardedBy("mEnqueuedOutputPipes") 667bb3bdce2d2eb8e279073420151e48a4a0fb60c7Tomasz Mikolajewski private final Set<ParcelFileDescriptor> mEnqueuedOutputPipes = new HashSet<>(); 677bb3bdce2d2eb8e279073420151e48a4a0fb60c7Tomasz Mikolajewski private final ThreadPoolExecutor mExecutor; 68d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski private final StrictJarFile mZipFile; 69d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 70d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski private ReadableArchive( 71d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Context context, 72d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski @Nullable File file, 73d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski @Nullable FileDescriptor fd, 74d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Uri archiveUri, 75d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski int accessMode, 76d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski @Nullable Uri notificationUri) 77d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throws IOException { 78d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski super(context, archiveUri, accessMode, notificationUri); 79d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (!supportsAccessMode(accessMode)) { 80d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new IllegalStateException("Unsupported access mode."); 81d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 82d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 833b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski // At most 8 active threads. All threads idling for more than a minute will 843b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski // be closed. 853b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski mExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS, 863b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski new LinkedBlockingQueue<Runnable>()); 873b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski mExecutor.allowCoreThreadTimeOut(true); 883b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski 89d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mZipFile = file != null ? 90d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski new StrictJarFile(file.getPath(), false /* verify */, 91d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski false /* signatures */) : 92d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski new StrictJarFile(fd, false /* verify */, false /* signatures */); 93d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 94d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ZipEntry entry; 95d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski String entryPath; 96d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final Iterator<ZipEntry> it = mZipFile.iterator(); 97d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final Stack<ZipEntry> stack = new Stack<>(); 98d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski while (it.hasNext()) { 99d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski entry = it.next(); 100d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (entry.isDirectory() != entry.getName().endsWith("/")) { 101d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new IOException( 102d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "Directories must have a trailing slash, and files must not."); 103d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 104d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski entryPath = getEntryPath(entry); 105d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (mEntries.containsKey(entryPath)) { 106d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new IOException("Multiple entries with the same name are not supported."); 107d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 108d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mEntries.put(entryPath, entry); 109d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (entry.isDirectory()) { 110d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mTree.put(entryPath, new ArrayList<ZipEntry>()); 111d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 112d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (!"/".equals(entryPath)) { // Skip root, as it doesn't have a parent. 113d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski stack.push(entry); 114d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 115d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 116d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 117d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski int delimiterIndex; 118d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski String parentPath; 119d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ZipEntry parentEntry; 120d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski List<ZipEntry> parentList; 121d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 122d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Go through all directories recursively and build a tree structure. 123d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski while (stack.size() > 0) { 124d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski entry = stack.pop(); 125d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 126d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski entryPath = getEntryPath(entry); 127d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski delimiterIndex = entryPath.lastIndexOf('/', entry.isDirectory() 128d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ? entryPath.length() - 2 : entryPath.length() - 1); 129d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentPath = entryPath.substring(0, delimiterIndex) + "/"; 130d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 131d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentList = mTree.get(parentPath); 132d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 133d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (parentList == null) { 134d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // The ZIP file doesn't contain all directories leading to the entry. 135d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // It's rare, but can happen in a valid ZIP archive. In such case create a 136d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // fake ZipEntry and add it on top of the stack to process it next. 137d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentEntry = new ZipEntry(parentPath); 138d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentEntry.setSize(0); 139d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentEntry.setTime(entry.getTime()); 140d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mEntries.put(parentPath, parentEntry); 141d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 142d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (!"/".equals(parentPath)) { 143d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski stack.push(parentEntry); 144d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 145d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 146d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentList = new ArrayList<>(); 147d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mTree.put(parentPath, parentList); 148d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 149d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 150d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski parentList.add(entry); 151d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 152d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 153d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 154d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski /** 155d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @see ParcelFileDescriptor 156d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski */ 157d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski public static boolean supportsAccessMode(int accessMode) { 158d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return accessMode == ParcelFileDescriptor.MODE_READ_ONLY; 159d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 160d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 161d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski /** 162d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * Creates a DocumentsArchive instance for opening, browsing and accessing 163d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * documents within the archive passed as a file descriptor. 164d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 165d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * If the file descriptor is not seekable, then a snapshot will be created. 166d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 167d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * This method takes ownership for the passed descriptor. The caller must 168a903c2cdad65d0fe163a4152faa6bd05e2888b22Tomasz Mikolajewski * not use it after passing. 169d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * 170d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @param context Context of the provider. 171d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @param descriptor File descriptor for the archive's contents. 172d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @param archiveUri Uri of the archive document. 173d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @param accessMode Access mode for the archive {@see ParcelFileDescriptor}. 174d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski * @param Uri notificationUri Uri for notifying that the archive file has changed. 175d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski */ 176d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski public static ReadableArchive createForParcelFileDescriptor( 177d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Context context, ParcelFileDescriptor descriptor, Uri archiveUri, int accessMode, 178d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski @Nullable Uri notificationUri) 179d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throws IOException { 180d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski FileDescriptor fd = null; 181d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try { 182d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (canSeek(descriptor)) { 183d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski fd = new FileDescriptor(); 184d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski fd.setInt$(descriptor.detachFd()); 185d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return new ReadableArchive(context, null, fd, archiveUri, accessMode, 186d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski notificationUri); 187d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 188d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 189d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Fallback for non-seekable file descriptors. 190d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski File snapshotFile = null; 191d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try { 192d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Create a copy of the archive, as ZipFile doesn't operate on streams. 193d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Moreover, ZipInputStream would be inefficient for large files on 194d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // pipes. 195d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski snapshotFile = File.createTempFile("com.android.documentsui.snapshot{", 196d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "}.zip", context.getCacheDir()); 197d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 198d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try ( 199d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final FileOutputStream outputStream = 200d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski new ParcelFileDescriptor.AutoCloseOutputStream( 201d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ParcelFileDescriptor.open( 202d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); 203d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ParcelFileDescriptor.AutoCloseInputStream inputStream = 204d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski new ParcelFileDescriptor.AutoCloseInputStream(descriptor); 205d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ) { 206d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final byte[] buffer = new byte[32 * 1024]; 207d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski int bytes; 208d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski while ((bytes = inputStream.read(buffer)) != -1) { 209d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski outputStream.write(buffer, 0, bytes); 210d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 211d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski outputStream.flush(); 212d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 213d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return new ReadableArchive(context, snapshotFile, null, archiveUri, accessMode, 214d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski notificationUri); 215d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } finally { 216d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // On UNIX the file will be still available for processes which opened it, even 217d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // after deleting it. Remove it ASAP, as it won't be used by anyone else. 218d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (snapshotFile != null) { 219d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski snapshotFile.delete(); 220d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 221d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 222d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } catch (Exception e) { 223d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Since the method takes ownership of the passed descriptor, close it 224d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // on exception. 225d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski IoUtils.closeQuietly(descriptor); 226d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski IoUtils.closeQuietly(fd); 227d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw e; 228d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 229d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 230d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 2317bb3bdce2d2eb8e279073420151e48a4a0fb60c7Tomasz Mikolajewski @Override 232d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski public ParcelFileDescriptor openDocument( 233d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski String documentId, String mode, @Nullable final CancellationSignal signal) 234d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throws FileNotFoundException { 235d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski MorePreconditions.checkArgumentEquals("r", mode, 236d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "Invalid mode. Only reading \"r\" supported, but got: \"%s\"."); 237d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); 238d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri, 239d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "Mismatching archive Uri. Expected: %s, actual: %s."); 240d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 241d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ZipEntry entry = mEntries.get(parsedId.mPath); 242d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (entry == null) { 243d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new FileNotFoundException(); 244d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 245d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 246d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski ParcelFileDescriptor[] pipe; 247d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try { 248d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski pipe = ParcelFileDescriptor.createReliablePipe(); 249d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } catch (IOException e) { 250d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Ideally we'd simply throw IOException to the caller, but for consistency 251d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // with DocumentsProvider::openDocument, converting it to IllegalStateException. 252d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new IllegalStateException("Failed to open the document.", e); 253d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 254d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final InputStream inputStream = mZipFile.getInputStream(entry); 255d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ParcelFileDescriptor outputPipe = pipe[1]; 25679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski 25779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski synchronized (mEnqueuedOutputPipes) { 25879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski mEnqueuedOutputPipes.add(outputPipe); 25979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 26079a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski 26179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski try { 26279a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski mExecutor.execute( 26379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski new Runnable() { 26479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski @Override 26579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski public void run() { 26679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski synchronized (mEnqueuedOutputPipes) { 26779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski mEnqueuedOutputPipes.remove(outputPipe); 26879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 26979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream = 27079a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) { 27179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski try { 27279a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski final byte buffer[] = new byte[32 * 1024]; 27379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski int bytes; 27479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski while ((bytes = inputStream.read(buffer)) != -1) { 27579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski if (Thread.interrupted()) { 27679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski throw new InterruptedException(); 27779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 27879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski if (signal != null) { 27979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski signal.throwIfCanceled(); 28079a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 28179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski outputStream.write(buffer, 0, bytes); 282d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 28379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (IOException | InterruptedException e) { 28479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski // Catch the exception before the outer try-with-resource closes 28579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski // the pipe with close() instead of closeWithError(). 28679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski try { 287a903c2cdad65d0fe163a4152faa6bd05e2888b22Tomasz Mikolajewski Log.e(TAG, "Failed while reading a file.", e); 288a903c2cdad65d0fe163a4152faa6bd05e2888b22Tomasz Mikolajewski outputPipe.closeWithError("Reading failure."); 28979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (IOException e2) { 29079a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski Log.e(TAG, "Failed to close the pipe after an error.", e2); 291d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 292d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 29379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (OperationCanceledException e) { 29479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski // Cancelled gracefully. 29579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (IOException e) { 29679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski Log.e(TAG, "Failed to close the output stream gracefully.", e); 29779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } finally { 29879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski IoUtils.closeQuietly(inputStream); 299d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 300d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 30179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski }); 30279a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (RejectedExecutionException e) { 30379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski IoUtils.closeQuietly(pipe[0]); 30479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski IoUtils.closeQuietly(pipe[1]); 30579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski synchronized (mEnqueuedOutputPipes) { 30679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski mEnqueuedOutputPipes.remove(outputPipe); 30779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 30879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski throw new IllegalStateException("Failed to initialize pipe."); 30979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 310d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 311d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return pipe[0]; 312d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 313d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 3147bb3bdce2d2eb8e279073420151e48a4a0fb60c7Tomasz Mikolajewski @Override 315d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski public AssetFileDescriptor openDocumentThumbnail( 316d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski String documentId, Point sizeHint, final CancellationSignal signal) 317d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throws FileNotFoundException { 318d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ArchiveId parsedId = ArchiveId.fromDocumentId(documentId); 319d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski MorePreconditions.checkArgumentEquals(mArchiveUri, parsedId.mArchiveUri, 320d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "Mismatching archive Uri. Expected: %s, actual: %s."); 321d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"), 322d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski "Thumbnails only supported for image/* MIME type."); 323d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 324d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ZipEntry entry = mEntries.get(parsedId.mPath); 325d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (entry == null) { 326d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski throw new FileNotFoundException(); 327d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 328d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 329d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski InputStream inputStream = null; 330d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try { 331d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski inputStream = mZipFile.getInputStream(entry); 332d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final ExifInterface exif = new ExifInterface(inputStream); 333d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski if (exif.hasThumbnail()) { 334d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Bundle extras = null; 335d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) { 336d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski case ExifInterface.ORIENTATION_ROTATE_90: 337d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras = new Bundle(1); 338d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 90); 339d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski break; 340d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski case ExifInterface.ORIENTATION_ROTATE_180: 341d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras = new Bundle(1); 342d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 180); 343d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski break; 344d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski case ExifInterface.ORIENTATION_ROTATE_270: 345d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras = new Bundle(1); 346d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 270); 347d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski break; 348d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 349d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski final long[] range = exif.getThumbnailRange(); 350d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return new AssetFileDescriptor( 351d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski openDocument(documentId, "r", signal), range[0], range[1], extras); 352d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 353d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } catch (IOException e) { 354d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Ignore the exception, as reading the EXIF may legally fail. 355d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski Log.e(TAG, "Failed to obtain thumbnail from EXIF.", e); 356d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } finally { 357d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski IoUtils.closeQuietly(inputStream); 358d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 359d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 360d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski return new AssetFileDescriptor( 361d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski openDocument(documentId, "r", signal), 0, entry.getSize(), null); 362d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 363d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski 3643b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski /** 3653b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski * Closes an archive. 3663b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski * 3673b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski * <p>This method does not block until shutdown. Once called, other methods should not be 3683b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski * called. Any active pipes will be terminated. 3693b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski */ 370d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski @Override 371d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski public void close() { 3723b135ef27a918143489cae0d99770890f3bbb3b6Tomasz Mikolajewski mExecutor.shutdownNow(); 37379a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski synchronized (mEnqueuedOutputPipes) { 37479a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski for (ParcelFileDescriptor outputPipe : mEnqueuedOutputPipes) { 37579a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski try { 37679a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski outputPipe.closeWithError("Archive closed."); 37779a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } catch (IOException e2) { 37879a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski // Silent close. 37979a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 38079a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 38179a4e10f62794bbb0ce676df7cc36c8edf5c510fTomasz Mikolajewski } 382d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski try { 383d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski mZipFile.close(); 384d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } catch (IOException e) { 385d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski // Silent close. 386d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 387d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski } 388d683f9756b0c56d28693c4d0269217e8fcebf76aTomasz Mikolajewski}; 389