blob: dbd948cbeffda91e1697fae84f98e9400fbfe5ca [file] [log] [blame]
Tomasz Mikolajewski55194742015-04-08 09:21:08 +09001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
19import android.content.Context;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070020import android.content.SharedPreferences;
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +090021import android.content.pm.ProviderInfo;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090022import android.content.res.AssetFileDescriptor;
23import android.database.Cursor;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090024import android.database.MatrixCursor;
Ben Kwac06f3fd2015-04-24 15:35:25 -070025import android.database.MatrixCursor.RowBuilder;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090026import android.graphics.Point;
Ben Kwac06f3fd2015-04-24 15:35:25 -070027import android.net.Uri;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070028import android.os.Bundle;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090029import android.os.CancellationSignal;
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +090030import android.os.FileUtils;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090031import android.os.ParcelFileDescriptor;
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +090032import android.provider.DocumentsContract;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090033import android.provider.DocumentsContract.Document;
34import android.provider.DocumentsContract.Root;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090035import android.provider.DocumentsProvider;
Ben Kwac06f3fd2015-04-24 15:35:25 -070036import android.support.annotation.VisibleForTesting;
Aga Wronska18410b72016-01-26 14:06:29 -080037import android.text.TextUtils;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070038import android.util.Log;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090039
Ben Kwaaac9e2e2015-04-16 18:14:35 -070040import libcore.io.IoUtils;
41
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090042import java.io.File;
43import java.io.FileNotFoundException;
Ben Kwac06f3fd2015-04-24 15:35:25 -070044import java.io.FileOutputStream;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090045import java.io.IOException;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070046import java.io.InputStream;
47import java.io.OutputStream;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +090048import java.util.ArrayList;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070049import java.util.Arrays;
50import java.util.Collection;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090051import java.util.HashMap;
Steve McKaybbeba522016-01-13 17:17:39 -080052import java.util.HashSet;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +090053import java.util.List;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070054import java.util.Map;
Steve McKaybbeba522016-01-13 17:17:39 -080055import java.util.Set;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090056
57public class StubProvider extends DocumentsProvider {
Steve McKay99bcc6a2015-10-26 17:03:55 -070058
59 public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stubprovider";
60 public static final String ROOT_0_ID = "TEST_ROOT_0";
61 public static final String ROOT_1_ID = "TEST_ROOT_1";
62
Steve McKaybbeba522016-01-13 17:17:39 -080063 public static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
64 public static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
65 public static final String EXTRA_PATH = "com.android.documentsui.stubprovider.PATH";
66 public static final String EXTRA_STREAM_TYPES
67 = "com.android.documentsui.stubprovider.STREAM_TYPES";
68 public static final String EXTRA_CONTENT = "com.android.documentsui.stubprovider.CONTENT";
69
Aga Wronska18410b72016-01-26 14:06:29 -080070 public static final String EXTRA_FLAGS = "com.android.documentsui.stubprovider.FLAGS";
71 public static final String EXTRA_PARENT_ID = "com.android.documentsui.stubprovider.PARENT";
72
Steve McKay99bcc6a2015-10-26 17:03:55 -070073 private static final String TAG = "StubProvider";
Steve McKaybbeba522016-01-13 17:17:39 -080074
Ben Kwaaac9e2e2015-04-16 18:14:35 -070075 private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
Aga Wronska18410b72016-01-26 14:06:29 -080076 private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
Steve McKay99bcc6a2015-10-26 17:03:55 -070077
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090078 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
79 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
80 Root.COLUMN_AVAILABLE_BYTES
81 };
82 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
83 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
84 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
85 };
86
Steve McKay99bcc6a2015-10-26 17:03:55 -070087 private final Map<String, StubDocument> mStorage = new HashMap<>();
88 private final Map<String, RootInfo> mRoots = new HashMap<>();
89 private final Object mWriteLock = new Object();
90
91 private String mAuthority = DEFAULT_AUTHORITY;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070092 private SharedPreferences mPrefs;
Steve McKaybbeba522016-01-13 17:17:39 -080093 private Set<String> mSimulateReadErrorIds = new HashSet<>();
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +090094
95 @Override
96 public void attachInfo(Context context, ProviderInfo info) {
97 mAuthority = info.authority;
98 super.attachInfo(context, info);
99 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900100
101 @Override
102 public boolean onCreate() {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700103 clearCacheAndBuildRoots();
104 return true;
105 }
106
Ben Kwac06f3fd2015-04-24 15:35:25 -0700107 @VisibleForTesting
108 public void clearCacheAndBuildRoots() {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700109 Log.d(TAG, "Resetting storage.");
110 removeChildrenRecursively(getContext().getCacheDir());
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700111 mStorage.clear();
Steve McKaybbeba522016-01-13 17:17:39 -0800112 mSimulateReadErrorIds.clear();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700113
114 mPrefs = getContext().getSharedPreferences(
115 "com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
116 Collection<String> rootIds = mPrefs.getStringSet("roots", null);
117 if (rootIds == null) {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700118 rootIds = Arrays.asList(new String[] { ROOT_0_ID, ROOT_1_ID });
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700119 }
Steve McKay99bcc6a2015-10-26 17:03:55 -0700120
121 mRoots.clear();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700122 for (String rootId : rootIds) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900123 // Make a subdir in the cache dir for each root.
124 final File file = new File(getContext().getCacheDir(), rootId);
125 if (file.mkdir()) {
126 Log.i(TAG, "Created new root directory @ " + file.getPath());
127 }
128 final RootInfo rootInfo = new RootInfo(file, getSize(rootId));
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800129
130 if(rootId.equals(ROOT_1_ID)) {
131 rootInfo.setSearchEnabled(false);
132 }
133
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900134 mStorage.put(rootInfo.document.documentId, rootInfo.document);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700135 mRoots.put(rootId, rootInfo);
136 }
137 }
138
139 /**
140 * @return Storage size, in bytes.
141 */
142 private long getSize(String rootId) {
143 final String key = STORAGE_SIZE_KEY + "." + rootId;
Steve McKay99bcc6a2015-10-26 17:03:55 -0700144 return mPrefs.getLong(key, DEFAULT_ROOT_SIZE);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900145 }
146
147 @Override
148 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700149 final MatrixCursor result = new MatrixCursor(projection != null ? projection
150 : DEFAULT_ROOT_PROJECTION);
151 for (Map.Entry<String, RootInfo> entry : mRoots.entrySet()) {
152 final String id = entry.getKey();
153 final RootInfo info = entry.getValue();
154 final RowBuilder row = result.newRow();
155 row.add(Root.COLUMN_ROOT_ID, id);
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800156 row.add(Root.COLUMN_FLAGS, info.flags);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700157 row.add(Root.COLUMN_TITLE, id);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700158 row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700159 row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
160 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900161 return result;
162 }
163
164 @Override
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700165 public Cursor queryDocument(String documentId, String[] projection)
166 throws FileNotFoundException {
167 final MatrixCursor result = new MatrixCursor(projection != null ? projection
168 : DEFAULT_DOCUMENT_PROJECTION);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900169 final StubDocument file = mStorage.get(documentId);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900170 if (file == null) {
171 throw new FileNotFoundException();
172 }
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900173 includeDocument(result, file);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900174 return result;
175 }
176
177 @Override
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900178 public boolean isChildDocument(String parentDocId, String docId) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900179 final StubDocument parentDocument = mStorage.get(parentDocId);
180 final StubDocument childDocument = mStorage.get(docId);
181 return FileUtils.contains(parentDocument.file, childDocument.file);
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900182 }
183
184 @Override
Steve McKay99bcc6a2015-10-26 17:03:55 -0700185 public String createDocument(String parentId, String mimeType, String displayName)
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900186 throws FileNotFoundException {
Aga Wronska18410b72016-01-26 14:06:29 -0800187 StubDocument parent = mStorage.get(parentId);
188 File file = createFile(parent, mimeType, displayName);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700189
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900190 final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
191 mStorage.put(document.documentId, document);
Ben Kwacb4461f2015-05-05 11:50:11 -0700192 Log.d(TAG, "Created document " + document.documentId);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900193 notifyParentChanged(document.parentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700194 getContext().getContentResolver().notifyChange(
195 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
196 null, false);
197
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900198 return document.documentId;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900199 }
200
201 @Override
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900202 public void deleteDocument(String documentId)
203 throws FileNotFoundException {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900204 final StubDocument document = mStorage.get(documentId);
205 final long fileSize = document.file.length();
206 if (document == null || !document.file.delete())
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900207 throw new FileNotFoundException();
208 synchronized (mWriteLock) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700209 document.rootInfo.size -= fileSize;
Ben Kwacb4461f2015-05-05 11:50:11 -0700210 mStorage.remove(documentId);
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900211 }
Ben Kwacb4461f2015-05-05 11:50:11 -0700212 Log.d(TAG, "Document deleted: " + documentId);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900213 notifyParentChanged(document.parentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700214 getContext().getContentResolver().notifyChange(
215 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
216 null, false);
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900217 }
218
219 @Override
Steve McKay31e104f2015-12-02 11:20:54 -0800220 public Cursor queryChildDocumentsForManage(String parentDocumentId, String[] projection,
221 String sortOrder) throws FileNotFoundException {
222 return queryChildDocuments(parentDocumentId, projection, sortOrder);
223 }
224
225 @Override
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900226 public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
227 throws FileNotFoundException {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900228 final StubDocument parentDocument = mStorage.get(parentDocumentId);
229 if (parentDocument == null || parentDocument.file.isFile()) {
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900230 throw new FileNotFoundException();
231 }
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700232 final MatrixCursor result = new MatrixCursor(projection != null ? projection
233 : DEFAULT_DOCUMENT_PROJECTION);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900234 result.setNotificationUri(getContext().getContentResolver(),
235 DocumentsContract.buildChildDocumentsUri(mAuthority, parentDocumentId));
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900236 StubDocument document;
237 for (File file : parentDocument.file.listFiles()) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700238 document = mStorage.get(getDocumentIdForFile(file));
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900239 if (document != null) {
240 includeDocument(result, document);
241 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900242 }
243 return result;
244 }
245
246 @Override
247 public Cursor queryRecentDocuments(String rootId, String[] projection)
248 throws FileNotFoundException {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700249 final MatrixCursor result = new MatrixCursor(projection != null ? projection
250 : DEFAULT_DOCUMENT_PROJECTION);
251 return result;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900252 }
253
254 @Override
Aga Wronska1f5b8922016-01-14 11:15:20 -0800255 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
256 throws FileNotFoundException {
257
258 StubDocument parentDocument = mRoots.get(rootId).document;
259 if (parentDocument == null || parentDocument.file.isFile()) {
260 throw new FileNotFoundException();
261 }
262
263 final MatrixCursor result = new MatrixCursor(
264 projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
265
266 for (File file : parentDocument.file.listFiles()) {
267 if (file.getName().toLowerCase().contains(query)) {
268 StubDocument document = mStorage.get(getDocumentIdForFile(file));
269 if (document != null) {
270 includeDocument(result, document);
271 }
272 }
273 }
274 return result;
275 }
276
277 @Override
Aga Wronska18410b72016-01-26 14:06:29 -0800278 public String renameDocument(String documentId, String displayName)
279 throws FileNotFoundException {
280
281 StubDocument oldDoc = mStorage.get(documentId);
282
283 File before = oldDoc.file;
284 File after = new File(before.getParentFile(), displayName);
285
286 if (after.exists()) {
287 throw new IllegalStateException("Already exists " + after);
288 }
289
290 boolean result = before.renameTo(after);
291
292 if (!result) {
293 throw new IllegalStateException("Failed to rename to " + after);
294 }
295
296 StubDocument newDoc = StubDocument.createRegularDocument(after, oldDoc.mimeType,
297 mStorage.get(oldDoc.parentId));
298
299 mStorage.remove(documentId);
300 notifyParentChanged(oldDoc.parentId);
301 getContext().getContentResolver().notifyChange(
302 DocumentsContract.buildDocumentUri(mAuthority, oldDoc.documentId), null, false);
303
304 mStorage.put(newDoc.documentId, newDoc);
305 notifyParentChanged(newDoc.parentId);
306 getContext().getContentResolver().notifyChange(
307 DocumentsContract.buildDocumentUri(mAuthority, newDoc.documentId), null, false);
308
309 if (!TextUtils.equals(documentId, newDoc.documentId)) {
310 return newDoc.documentId;
311 } else {
312 return null;
313 }
314 }
315
316 @Override
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900317 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
318 throws FileNotFoundException {
Steve McKaybbeba522016-01-13 17:17:39 -0800319
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900320 final StubDocument document = mStorage.get(docId);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900321 if (document == null || !document.file.isFile()) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900322 throw new FileNotFoundException();
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900323 }
324 if ((document.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) {
325 throw new IllegalStateException("Tried to open a virtual file.");
326 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900327
328 if ("r".equals(mode)) {
Steve McKaybbeba522016-01-13 17:17:39 -0800329 if (mSimulateReadErrorIds.contains(docId)) {
330 Log.d(TAG, "Simulated errs enabled. Open in the wrong mode.");
331 return ParcelFileDescriptor.open(
332 document.file, ParcelFileDescriptor.MODE_WRITE_ONLY);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700333 }
Steve McKaybbeba522016-01-13 17:17:39 -0800334 return ParcelFileDescriptor.open(document.file, ParcelFileDescriptor.MODE_READ_ONLY);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900335 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900336 if ("w".equals(mode)) {
337 return startWrite(document);
338 }
339
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900340 throw new FileNotFoundException();
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900341 }
342
Ben Kwac06f3fd2015-04-24 15:35:25 -0700343 @VisibleForTesting
Ben Kwacb4461f2015-05-05 11:50:11 -0700344 public void simulateReadErrorsForFile(Uri uri) {
Steve McKaybbeba522016-01-13 17:17:39 -0800345 simulateReadErrorsForFile(DocumentsContract.getDocumentId(uri));
346 }
347
348 public void simulateReadErrorsForFile(String id) {
349 mSimulateReadErrorIds.add(id);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700350 }
351
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900352 @Override
353 public AssetFileDescriptor openDocumentThumbnail(
354 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
355 throw new FileNotFoundException();
356 }
357
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900358 @Override
359 public AssetFileDescriptor openTypedDocument(
Steve McKaybbeba522016-01-13 17:17:39 -0800360 String docId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900361 throws FileNotFoundException {
Steve McKaybbeba522016-01-13 17:17:39 -0800362 final StubDocument document = mStorage.get(docId);
Tomasz Mikolajewskie475d3b2015-12-24 13:24:00 +0900363 if (document == null || !document.file.isFile() || document.streamTypes == null) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900364 throw new FileNotFoundException();
365 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900366 for (final String mimeType : document.streamTypes) {
367 // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900368 // doesn't use them for getStreamTypes nor openTypedDocument.
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900369 if (mimeType.equals(mimeTypeFilter)) {
370 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
371 document.file, ParcelFileDescriptor.MODE_READ_ONLY);
Steve McKaybbeba522016-01-13 17:17:39 -0800372 if (mSimulateReadErrorIds.contains(docId)) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900373 pfd = new ParcelFileDescriptor(pfd) {
374 @Override
375 public void checkError() throws IOException {
376 throw new IOException("Test error");
377 }
378 };
379 }
380 return new AssetFileDescriptor(pfd, 0, document.file.length());
381 }
382 }
383 throw new IllegalArgumentException("Invalid MIME type filter for openTypedDocument().");
384 }
385
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900386 @Override
387 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
388 final StubDocument document = mStorage.get(DocumentsContract.getDocumentId(uri));
389 if (document == null) {
390 throw new IllegalArgumentException(
391 "The provided Uri is incorrect, or the file is gone.");
392 }
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900393 if (!"*/*".equals(mimeTypeFilter)) {
394 // Not used by DocumentsUI, so don't bother implementing it.
395 throw new UnsupportedOperationException();
396 }
Tomasz Mikolajewskie475d3b2015-12-24 13:24:00 +0900397 if (document.streamTypes == null) {
398 return null;
399 }
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900400 return document.streamTypes.toArray(new String[document.streamTypes.size()]);
401 }
402
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900403 private ParcelFileDescriptor startWrite(final StubDocument document)
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900404 throws FileNotFoundException {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900405 ParcelFileDescriptor[] pipe;
406 try {
407 pipe = ParcelFileDescriptor.createReliablePipe();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700408 } catch (IOException exception) {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900409 throw new FileNotFoundException();
410 }
411 final ParcelFileDescriptor readPipe = pipe[0];
412 final ParcelFileDescriptor writePipe = pipe[1];
413
414 new Thread() {
415 @Override
416 public void run() {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700417 InputStream inputStream = null;
418 OutputStream outputStream = null;
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900419 try {
Ben Kwacb4461f2015-05-05 11:50:11 -0700420 Log.d(TAG, "Opening write stream on file " + document.documentId);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700421 inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPipe);
422 outputStream = new FileOutputStream(document.file);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900423 byte[] buffer = new byte[32 * 1024];
424 int bytesToRead;
425 int bytesRead = 0;
426 while (bytesRead != -1) {
427 synchronized (mWriteLock) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700428 // This cast is safe because the max possible value is buffer.length.
429 bytesToRead = (int) Math.min(document.rootInfo.getRemainingCapacity(),
430 buffer.length);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900431 if (bytesToRead == 0) {
432 closePipeWithErrorSilently(readPipe, "Not enough space.");
433 break;
434 }
435 bytesRead = inputStream.read(buffer, 0, bytesToRead);
436 if (bytesRead == -1) {
437 break;
438 }
439 outputStream.write(buffer, 0, bytesRead);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700440 document.rootInfo.size += bytesRead;
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900441 }
442 }
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700443 } catch (IOException e) {
Ben Kwac06f3fd2015-04-24 15:35:25 -0700444 Log.e(TAG, "Error on close", e);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900445 closePipeWithErrorSilently(readPipe, e.getMessage());
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700446 } finally {
447 IoUtils.closeQuietly(inputStream);
448 IoUtils.closeQuietly(outputStream);
Ben Kwacb4461f2015-05-05 11:50:11 -0700449 Log.d(TAG, "Closing write stream on file " + document.documentId);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900450 notifyParentChanged(document.parentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700451 getContext().getContentResolver().notifyChange(
452 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
453 null, false);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900454 }
455 }
456 }.start();
457
458 return writePipe;
459 }
460
461 private void closePipeWithErrorSilently(ParcelFileDescriptor pipe, String error) {
462 try {
463 pipe.closeWithError(error);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700464 } catch (IOException ignore) {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900465 }
466 }
467
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700468 @Override
469 public Bundle call(String method, String arg, Bundle extras) {
Steve McKaya495c482016-02-04 10:02:09 -0800470 // We're not supposed to override any of the default DocumentsProvider
471 // methods that are supported by "call", so javadoc asks that we
472 // always call super.call first and return if response is not null.
473 Bundle result = super.call(method, arg, extras);
474 if (result != null) {
475 return result;
476 }
477
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700478 switch (method) {
479 case "clear":
480 clearCacheAndBuildRoots();
481 return null;
482 case "configure":
483 configure(arg, extras);
484 return null;
Steve McKaybbeba522016-01-13 17:17:39 -0800485 case "createVirtualFile":
486 return createVirtualFileFromBundle(extras);
487 case "simulateReadErrorsForFile":
488 simulateReadErrorsForFile(arg);
489 return null;
Aga Wronska18410b72016-01-26 14:06:29 -0800490 case "createDocumentWithFlags":
Steve McKaya495c482016-02-04 10:02:09 -0800491 return dispatchCreateDocumentWithFlags(extras);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900492 }
Steve McKaya495c482016-02-04 10:02:09 -0800493
494 return null;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700495 }
496
Steve McKaybbeba522016-01-13 17:17:39 -0800497 private Bundle createVirtualFileFromBundle(Bundle extras) {
498 try {
499 Uri uri = createVirtualFile(
500 extras.getString(EXTRA_ROOT),
501 extras.getString(EXTRA_PATH),
502 extras.getString(Document.COLUMN_MIME_TYPE),
503 extras.getStringArrayList(EXTRA_STREAM_TYPES),
504 extras.getByteArray(EXTRA_CONTENT));
505
506 String documentId = DocumentsContract.getDocumentId(uri);
507 Bundle result = new Bundle();
508 result.putString(Document.COLUMN_DOCUMENT_ID, documentId);
509 return result;
510 } catch (IOException e) {
511 Log.e(TAG, "Couldn't create virtual file.");
512 }
513
514 return null;
515 }
516
Aga Wronska18410b72016-01-26 14:06:29 -0800517 private Bundle dispatchCreateDocumentWithFlags(Bundle extras) {
518 String rootId = extras.getString(EXTRA_PARENT_ID);
519 String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
520 String name = extras.getString(Document.COLUMN_DISPLAY_NAME);
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900521 List<String> streamTypes = extras.getStringArrayList(EXTRA_STREAM_TYPES);
Aga Wronska18410b72016-01-26 14:06:29 -0800522 int flags = extras.getInt(EXTRA_FLAGS);
523
524 Bundle out = new Bundle();
525 String documentId = null;
526 try {
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900527 documentId = createDocument(rootId, mimeType, name, flags, streamTypes);
Aga Wronska18410b72016-01-26 14:06:29 -0800528 Uri uri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
529 out.putParcelable(DocumentsContract.EXTRA_URI, uri);
530 } catch (FileNotFoundException e) {
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900531 Log.d(TAG, "Creating document with flags failed" + name);
Aga Wronska18410b72016-01-26 14:06:29 -0800532 }
533 return out;
534 }
535
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900536 public String createDocument(String parentId, String mimeType, String displayName, int flags,
537 List<String> streamTypes) throws FileNotFoundException {
Aga Wronska18410b72016-01-26 14:06:29 -0800538
539 StubDocument parent = mStorage.get(parentId);
540 File file = createFile(parent, mimeType, displayName);
541
542 final StubDocument document = StubDocument.createDocumentWithFlags(file, mimeType, parent,
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900543 flags, streamTypes);
Aga Wronska18410b72016-01-26 14:06:29 -0800544 mStorage.put(document.documentId, document);
545 Log.d(TAG, "Created document " + document.documentId);
546 notifyParentChanged(document.parentId);
547 getContext().getContentResolver().notifyChange(
548 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
549 null, false);
550
551 return document.documentId;
552 }
553
554 private File createFile(StubDocument parent, String mimeType, String displayName)
555 throws FileNotFoundException {
556 if (parent == null) {
557 throw new IllegalArgumentException(
558 "Can't create file " + displayName + " in null parent.");
559 }
560 if (!parent.file.isDirectory()) {
561 throw new IllegalArgumentException(
562 "Can't create file " + displayName + " inside non-directory parent "
563 + parent.file.getName());
564 }
565
566 final File file = new File(parent.file, displayName);
567 if (file.exists()) {
568 throw new FileNotFoundException(
569 "Duplicate file names not supported for " + file);
570 }
571
572 if (mimeType.equals(Document.MIME_TYPE_DIR)) {
573 if (!file.mkdirs()) {
574 throw new FileNotFoundException("Failed to create directory(s): " + file);
575 }
576 Log.i(TAG, "Created new directory: " + file);
577 } else {
578 boolean created = false;
579 try {
580 created = file.createNewFile();
581 } catch (IOException e) {
582 // We'll throw an FNF exception later :)
583 Log.e(TAG, "createNewFile operation failed for file: " + file, e);
584 }
585 if (!created) {
586 throw new FileNotFoundException("createNewFile operation failed for: " + file);
587 }
588 Log.i(TAG, "Created new file: " + file);
589 }
590 return file;
591 }
592
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700593 private void configure(String arg, Bundle extras) {
594 Log.d(TAG, "Configure " + arg);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700595 String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700596 long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
597 setSize(rootName, rootSize);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900598 }
599
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900600 private void notifyParentChanged(String parentId) {
601 getContext().getContentResolver().notifyChange(
602 DocumentsContract.buildChildDocumentsUri(mAuthority, parentId), null, false);
603 // Notify also about possible change in remaining space on the root.
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700604 getContext().getContentResolver().notifyChange(DocumentsContract.buildRootsUri(mAuthority),
605 null, false);
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900606 }
607
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900608 private void includeDocument(MatrixCursor result, StubDocument document) {
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900609 final RowBuilder row = result.newRow();
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900610 row.add(Document.COLUMN_DOCUMENT_ID, document.documentId);
611 row.add(Document.COLUMN_DISPLAY_NAME, document.file.getName());
612 row.add(Document.COLUMN_SIZE, document.file.length());
613 row.add(Document.COLUMN_MIME_TYPE, document.mimeType);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900614 row.add(Document.COLUMN_FLAGS, document.flags);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900615 row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified());
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900616 }
617
Steve McKay99bcc6a2015-10-26 17:03:55 -0700618 private void removeChildrenRecursively(File file) {
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900619 for (File childFile : file.listFiles()) {
620 if (childFile.isDirectory()) {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700621 removeChildrenRecursively(childFile);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900622 }
623 childFile.delete();
624 }
625 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900626
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700627 public void setSize(String rootId, long rootSize) {
628 RootInfo root = mRoots.get(rootId);
629 if (root != null) {
630 final String key = STORAGE_SIZE_KEY + "." + rootId;
631 Log.d(TAG, "Set size of " + key + " : " + rootSize);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900632
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700633 // Persist the size.
634 SharedPreferences.Editor editor = mPrefs.edit();
635 editor.putLong(key, rootSize);
636 editor.apply();
637 // Apply the size in the current instance of this provider.
638 root.capacity = rootSize;
639 getContext().getContentResolver().notifyChange(
640 DocumentsContract.buildRootsUri(mAuthority),
641 null, false);
642 } else {
643 Log.e(TAG, "Attempt to configure non-existent root: " + rootId);
644 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900645 }
646
Ben Kwac06f3fd2015-04-24 15:35:25 -0700647 @VisibleForTesting
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900648 public Uri createRegularFile(String rootId, String path, String mimeType, byte[] content)
649 throws FileNotFoundException, IOException {
650 final File file = createFile(rootId, path, mimeType, content);
651 final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
652 if (parent == null) {
653 throw new FileNotFoundException("Parent not found.");
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700654 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900655 final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
656 mStorage.put(document.documentId, document);
Aga Wronska18410b72016-01-26 14:06:29 -0800657 return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700658 }
659
660 @VisibleForTesting
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900661 public Uri createVirtualFile(
662 String rootId, String path, String mimeType, List<String> streamTypes, byte[] content)
663 throws FileNotFoundException, IOException {
Steve McKaybbeba522016-01-13 17:17:39 -0800664
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900665 final File file = createFile(rootId, path, mimeType, content);
666 final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
667 if (parent == null) {
668 throw new FileNotFoundException("Parent not found.");
669 }
670 final StubDocument document = StubDocument.createVirtualDocument(
671 file, mimeType, streamTypes, parent);
672 mStorage.put(document.documentId, document);
Aga Wronska18410b72016-01-26 14:06:29 -0800673 return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900674 }
675
676 @VisibleForTesting
Ben Kwac06f3fd2015-04-24 15:35:25 -0700677 public File getFile(String rootId, String path) throws FileNotFoundException {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700678 StubDocument root = mRoots.get(rootId).document;
Ben Kwac06f3fd2015-04-24 15:35:25 -0700679 if (root == null) {
680 throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
681 }
682 // Convert the path string into a path that's relative to the root.
683 File needle = new File(root.file, path.substring(1));
684
685 StubDocument found = mStorage.get(getDocumentIdForFile(needle));
686 if (found == null) {
687 return null;
688 }
689 return found.file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700690 }
691
Tomasz Mikolajewski049eb562015-12-18 11:29:48 +0900692 private File createFile(String rootId, String path, String mimeType, byte[] content)
693 throws FileNotFoundException, IOException {
Steve McKaybbeba522016-01-13 17:17:39 -0800694 Log.d(TAG, "Creating test file " + rootId + " : " + path);
Tomasz Mikolajewski049eb562015-12-18 11:29:48 +0900695 StubDocument root = mRoots.get(rootId).document;
696 if (root == null) {
697 throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
698 }
699 final File file = new File(root.file, path.substring(1));
700 if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
701 if (!file.mkdirs()) {
702 throw new FileNotFoundException("Couldn't create directory " + file.getPath());
703 }
704 } else {
705 if (!file.createNewFile()) {
706 throw new FileNotFoundException("Couldn't create file " + file.getPath());
707 }
708 try (final FileOutputStream fout = new FileOutputStream(file)) {
709 fout.write(content);
710 }
711 }
712 return file;
713 }
714
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900715 final static class RootInfo {
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800716 private static final int DEFAULT_ROOTS_FLAGS = Root.FLAG_SUPPORTS_SEARCH
717 | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD;
718
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700719 public final String name;
Steve McKay99bcc6a2015-10-26 17:03:55 -0700720 public final StubDocument document;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700721 public long capacity;
722 public long size;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800723 public int flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700724
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900725 RootInfo(File file, long capacity) {
726 this.name = file.getName();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700727 this.capacity = 1024 * 1024;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800728 this.flags = DEFAULT_ROOTS_FLAGS;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700729 this.capacity = capacity;
730 this.size = 0;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800731 this.document = StubDocument.createRootDocument(file, this);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700732 }
733
734 public long getRemainingCapacity() {
735 return capacity - size;
736 }
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800737
738 public void setSearchEnabled(boolean enabled) {
739 flags = enabled ? (flags | Root.FLAG_SUPPORTS_SEARCH)
740 : (flags & ~Root.FLAG_SUPPORTS_SEARCH);
741 }
742
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700743 }
744
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900745 final static class StubDocument {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700746 public final File file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700747 public final String documentId;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900748 public final String mimeType;
749 public final List<String> streamTypes;
750 public final int flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700751 public final String parentId;
752 public final RootInfo rootInfo;
753
Aga Wronska18410b72016-01-26 14:06:29 -0800754 private StubDocument(File file, String mimeType, List<String> streamTypes, int flags,
755 StubDocument parent) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700756 this.file = file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700757 this.documentId = getDocumentIdForFile(file);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900758 this.mimeType = mimeType;
759 this.streamTypes = streamTypes;
760 this.flags = flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700761 this.parentId = parent.documentId;
762 this.rootInfo = parent.rootInfo;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700763 }
764
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900765 private StubDocument(File file, RootInfo rootInfo) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700766 this.file = file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700767 this.documentId = getDocumentIdForFile(file);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900768 this.mimeType = Document.MIME_TYPE_DIR;
Steve McKayb324dfd2016-10-28 12:52:36 -0700769 this.streamTypes = new ArrayList<>();
Aga Wronska3c237182016-01-20 16:32:33 -0800770 this.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_SUPPORTS_RENAME;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700771 this.parentId = null;
772 this.rootInfo = rootInfo;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700773 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900774
775 public static StubDocument createRootDocument(File file, RootInfo rootInfo) {
776 return new StubDocument(file, rootInfo);
777 }
778
779 public static StubDocument createRegularDocument(
780 File file, String mimeType, StubDocument parent) {
Aga Wronska18410b72016-01-26 14:06:29 -0800781 int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900782 if (file.isDirectory()) {
783 flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
784 } else {
785 flags |= Document.FLAG_SUPPORTS_WRITE;
786 }
787 return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent);
788 }
789
Aga Wronska18410b72016-01-26 14:06:29 -0800790 public static StubDocument createDocumentWithFlags(
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900791 File file, String mimeType, StubDocument parent, int flags,
792 List<String> streamTypes) {
793 return new StubDocument(file, mimeType, streamTypes, flags, parent);
Aga Wronska18410b72016-01-26 14:06:29 -0800794 }
795
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900796 public static StubDocument createVirtualDocument(
797 File file, String mimeType, List<String> streamTypes, StubDocument parent) {
798 int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
799 | Document.FLAG_VIRTUAL_DOCUMENT;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900800 return new StubDocument(file, mimeType, streamTypes, flags, parent);
801 }
802
Steve McKayf712a202015-11-19 17:27:12 -0800803 @Override
804 public String toString() {
805 return "StubDocument{"
806 + "path:" + file.getPath()
Steve McKayf712a202015-11-19 17:27:12 -0800807 + ", documentId:" + documentId
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900808 + ", mimeType:" + mimeType
809 + ", streamTypes:" + streamTypes.toString()
810 + ", flags:" + flags
Steve McKayf712a202015-11-19 17:27:12 -0800811 + ", parentId:" + parentId
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900812 + ", rootInfo:" + rootInfo
Steve McKayf712a202015-11-19 17:27:12 -0800813 + "}";
814 }
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700815 }
816
817 private static String getDocumentIdForFile(File file) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900818 return file.getAbsolutePath();
819 }
820}