blob: 521df53c0f74fd1d0dc83311994fb5a042106c39 [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
Garfield Tan15666182017-01-18 16:27:55 -080019import android.content.ContentResolver;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090020import android.content.Context;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070021import android.content.SharedPreferences;
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +090022import android.content.pm.ProviderInfo;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090023import android.content.res.AssetFileDescriptor;
24import android.database.Cursor;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090025import android.database.MatrixCursor;
Ben Kwac06f3fd2015-04-24 15:35:25 -070026import android.database.MatrixCursor.RowBuilder;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090027import android.graphics.Point;
Ben Kwac06f3fd2015-04-24 15:35:25 -070028import android.net.Uri;
Tony Huang91df8712019-11-18 19:57:32 +080029import android.os.Bundle;
30import android.os.CancellationSignal;
31import android.os.FileUtils;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.ParcelFileDescriptor;
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +090035import android.provider.DocumentsContract;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090036import android.provider.DocumentsContract.Document;
37import android.provider.DocumentsContract.Root;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090038import android.provider.DocumentsProvider;
Aga Wronska18410b72016-01-26 14:06:29 -080039import android.text.TextUtils;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070040import android.util.Log;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090041
Tony Huang91df8712019-11-18 19:57:32 +080042import androidx.annotation.VisibleForTesting;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070043
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090044import java.io.File;
45import java.io.FileNotFoundException;
Ben Kwac06f3fd2015-04-24 15:35:25 -070046import java.io.FileOutputStream;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090047import java.io.IOException;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070048import java.io.InputStream;
49import java.io.OutputStream;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +090050import java.util.ArrayList;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070051import java.util.Arrays;
52import java.util.Collection;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090053import java.util.HashMap;
Steve McKaybbeba522016-01-13 17:17:39 -080054import java.util.HashSet;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +090055import java.util.List;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070056import java.util.Map;
Steve McKaybbeba522016-01-13 17:17:39 -080057import java.util.Set;
Garfield Tan1e80be72017-02-27 18:16:00 -080058import java.util.concurrent.CountDownLatch;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090059
60public class StubProvider extends DocumentsProvider {
Steve McKay99bcc6a2015-10-26 17:03:55 -070061
62 public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stubprovider";
63 public static final String ROOT_0_ID = "TEST_ROOT_0";
64 public static final String ROOT_1_ID = "TEST_ROOT_1";
65
Steve McKaybbeba522016-01-13 17:17:39 -080066 public static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
67 public static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
68 public static final String EXTRA_PATH = "com.android.documentsui.stubprovider.PATH";
69 public static final String EXTRA_STREAM_TYPES
70 = "com.android.documentsui.stubprovider.STREAM_TYPES";
71 public static final String EXTRA_CONTENT = "com.android.documentsui.stubprovider.CONTENT";
Garfield Tanceedd1f2017-05-17 14:10:12 -070072 public static final String EXTRA_ENABLE_ROOT_NOTIFICATION
73 = "com.android.documentsui.stubprovider.ROOT_NOTIFICATION";
Steve McKaybbeba522016-01-13 17:17:39 -080074
Aga Wronska18410b72016-01-26 14:06:29 -080075 public static final String EXTRA_FLAGS = "com.android.documentsui.stubprovider.FLAGS";
76 public static final String EXTRA_PARENT_ID = "com.android.documentsui.stubprovider.PARENT";
77
Steve McKay99bcc6a2015-10-26 17:03:55 -070078 private static final String TAG = "StubProvider";
Steve McKaybbeba522016-01-13 17:17:39 -080079
Ben Kwaaac9e2e2015-04-16 18:14:35 -070080 private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
Jun Ono418e5a32017-11-22 10:29:30 +090081 private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 500; // 500 MB.
Steve McKay99bcc6a2015-10-26 17:03:55 -070082
Tomasz Mikolajewski55194742015-04-08 09:21:08 +090083 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
84 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
85 Root.COLUMN_AVAILABLE_BYTES
86 };
87 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
88 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
89 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
90 };
91
Steve McKay99bcc6a2015-10-26 17:03:55 -070092 private final Map<String, StubDocument> mStorage = new HashMap<>();
93 private final Map<String, RootInfo> mRoots = new HashMap<>();
94 private final Object mWriteLock = new Object();
95
96 private String mAuthority = DEFAULT_AUTHORITY;
Ben Kwaaac9e2e2015-04-16 18:14:35 -070097 private SharedPreferences mPrefs;
Steve McKaybbeba522016-01-13 17:17:39 -080098 private Set<String> mSimulateReadErrorIds = new HashSet<>();
Garfield Tan15666182017-01-18 16:27:55 -080099 private long mLoadingDuration = 0;
Garfield Tanceedd1f2017-05-17 14:10:12 -0700100 private boolean mRootNotification = true;
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900101
102 @Override
103 public void attachInfo(Context context, ProviderInfo info) {
104 mAuthority = info.authority;
105 super.attachInfo(context, info);
106 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900107
108 @Override
109 public boolean onCreate() {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700110 clearCacheAndBuildRoots();
111 return true;
112 }
113
Ben Kwac06f3fd2015-04-24 15:35:25 -0700114 @VisibleForTesting
115 public void clearCacheAndBuildRoots() {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700116 Log.d(TAG, "Resetting storage.");
117 removeChildrenRecursively(getContext().getCacheDir());
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700118 mStorage.clear();
Steve McKaybbeba522016-01-13 17:17:39 -0800119 mSimulateReadErrorIds.clear();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700120
121 mPrefs = getContext().getSharedPreferences(
122 "com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
123 Collection<String> rootIds = mPrefs.getStringSet("roots", null);
124 if (rootIds == null) {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700125 rootIds = Arrays.asList(new String[] { ROOT_0_ID, ROOT_1_ID });
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700126 }
Steve McKay99bcc6a2015-10-26 17:03:55 -0700127
128 mRoots.clear();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700129 for (String rootId : rootIds) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900130 // Make a subdir in the cache dir for each root.
131 final File file = new File(getContext().getCacheDir(), rootId);
132 if (file.mkdir()) {
133 Log.i(TAG, "Created new root directory @ " + file.getPath());
134 }
135 final RootInfo rootInfo = new RootInfo(file, getSize(rootId));
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800136
137 if(rootId.equals(ROOT_1_ID)) {
138 rootInfo.setSearchEnabled(false);
139 }
140
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900141 mStorage.put(rootInfo.document.documentId, rootInfo.document);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700142 mRoots.put(rootId, rootInfo);
143 }
Garfield Tan15666182017-01-18 16:27:55 -0800144
145 mLoadingDuration = 0;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700146 }
147
148 /**
149 * @return Storage size, in bytes.
150 */
151 private long getSize(String rootId) {
152 final String key = STORAGE_SIZE_KEY + "." + rootId;
Steve McKay99bcc6a2015-10-26 17:03:55 -0700153 return mPrefs.getLong(key, DEFAULT_ROOT_SIZE);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900154 }
155
156 @Override
157 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700158 final MatrixCursor result = new MatrixCursor(projection != null ? projection
159 : DEFAULT_ROOT_PROJECTION);
160 for (Map.Entry<String, RootInfo> entry : mRoots.entrySet()) {
161 final String id = entry.getKey();
162 final RootInfo info = entry.getValue();
163 final RowBuilder row = result.newRow();
164 row.add(Root.COLUMN_ROOT_ID, id);
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800165 row.add(Root.COLUMN_FLAGS, info.flags);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700166 row.add(Root.COLUMN_TITLE, id);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700167 row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700168 row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
169 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900170 return result;
171 }
172
173 @Override
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700174 public Cursor queryDocument(String documentId, String[] projection)
175 throws FileNotFoundException {
176 final MatrixCursor result = new MatrixCursor(projection != null ? projection
177 : DEFAULT_DOCUMENT_PROJECTION);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900178 final StubDocument file = mStorage.get(documentId);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900179 if (file == null) {
180 throw new FileNotFoundException();
181 }
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900182 includeDocument(result, file);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900183 return result;
184 }
185
186 @Override
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900187 public boolean isChildDocument(String parentDocId, String docId) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900188 final StubDocument parentDocument = mStorage.get(parentDocId);
189 final StubDocument childDocument = mStorage.get(docId);
Tony Huang91df8712019-11-18 19:57:32 +0800190
191 if (parentDocument.file == null || childDocument.file == null) {
192 return false;
193 }
194
195 return contains(
196 parentDocument.file.getAbsolutePath(), childDocument.file.getAbsolutePath());
197 }
198
199 private static boolean contains(String dirPath, String filePath) {
200 if (dirPath.equals(filePath)) {
201 return true;
202 }
203 if (!dirPath.endsWith("/")) {
204 dirPath += "/";
205 }
206 return filePath.startsWith(dirPath);
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900207 }
208
209 @Override
Steve McKay99bcc6a2015-10-26 17:03:55 -0700210 public String createDocument(String parentId, String mimeType, String displayName)
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900211 throws FileNotFoundException {
Aga Wronska18410b72016-01-26 14:06:29 -0800212 StubDocument parent = mStorage.get(parentId);
213 File file = createFile(parent, mimeType, displayName);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700214
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900215 final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
216 mStorage.put(document.documentId, document);
Ben Kwacb4461f2015-05-05 11:50:11 -0700217 Log.d(TAG, "Created document " + document.documentId);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900218 notifyParentChanged(document.parentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700219 getContext().getContentResolver().notifyChange(
220 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
221 null, false);
222
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900223 return document.documentId;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900224 }
225
226 @Override
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900227 public void deleteDocument(String documentId)
228 throws FileNotFoundException {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900229 final StubDocument document = mStorage.get(documentId);
230 final long fileSize = document.file.length();
231 if (document == null || !document.file.delete())
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900232 throw new FileNotFoundException();
233 synchronized (mWriteLock) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700234 document.rootInfo.size -= fileSize;
Ben Kwacb4461f2015-05-05 11:50:11 -0700235 mStorage.remove(documentId);
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900236 }
Ben Kwacb4461f2015-05-05 11:50:11 -0700237 Log.d(TAG, "Document deleted: " + documentId);
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900238 notifyParentChanged(document.parentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700239 getContext().getContentResolver().notifyChange(
240 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
241 null, false);
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900242 }
243
244 @Override
Steve McKay31e104f2015-12-02 11:20:54 -0800245 public Cursor queryChildDocumentsForManage(String parentDocumentId, String[] projection,
246 String sortOrder) throws FileNotFoundException {
247 return queryChildDocuments(parentDocumentId, projection, sortOrder);
248 }
249
250 @Override
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900251 public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
252 throws FileNotFoundException {
Garfield Tan15666182017-01-18 16:27:55 -0800253 if (mLoadingDuration > 0) {
254 final Uri notifyUri = DocumentsContract.buildDocumentUri(mAuthority, parentDocumentId);
255 final ContentResolver resolver = getContext().getContentResolver();
256 new Handler(Looper.getMainLooper()).postDelayed(
257 () -> resolver.notifyChange(notifyUri, null, false),
258 mLoadingDuration);
259 mLoadingDuration = 0;
260
261 MatrixCursor cursor = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
262 Bundle bundle = new Bundle();
263 bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
264 cursor.setExtras(bundle);
265 cursor.setNotificationUri(resolver, notifyUri);
266 return cursor;
267 } else {
268 final StubDocument parentDocument = mStorage.get(parentDocumentId);
269 if (parentDocument == null || parentDocument.file.isFile()) {
270 throw new FileNotFoundException();
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900271 }
Garfield Tan15666182017-01-18 16:27:55 -0800272 final MatrixCursor result = new MatrixCursor(projection != null ? projection
273 : DEFAULT_DOCUMENT_PROJECTION);
274 result.setNotificationUri(getContext().getContentResolver(),
275 DocumentsContract.buildChildDocumentsUri(mAuthority, parentDocumentId));
276 StubDocument document;
277 for (File file : parentDocument.file.listFiles()) {
278 document = mStorage.get(getDocumentIdForFile(file));
279 if (document != null) {
280 includeDocument(result, document);
281 }
282 }
283 return result;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900284 }
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900285 }
286
287 @Override
288 public Cursor queryRecentDocuments(String rootId, String[] projection)
289 throws FileNotFoundException {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700290 final MatrixCursor result = new MatrixCursor(projection != null ? projection
291 : DEFAULT_DOCUMENT_PROJECTION);
292 return result;
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900293 }
294
295 @Override
Aga Wronska1f5b8922016-01-14 11:15:20 -0800296 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
297 throws FileNotFoundException {
298
299 StubDocument parentDocument = mRoots.get(rootId).document;
300 if (parentDocument == null || parentDocument.file.isFile()) {
301 throw new FileNotFoundException();
302 }
303
304 final MatrixCursor result = new MatrixCursor(
305 projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
306
307 for (File file : parentDocument.file.listFiles()) {
308 if (file.getName().toLowerCase().contains(query)) {
309 StubDocument document = mStorage.get(getDocumentIdForFile(file));
310 if (document != null) {
311 includeDocument(result, document);
312 }
313 }
314 }
315 return result;
316 }
317
318 @Override
Aga Wronska18410b72016-01-26 14:06:29 -0800319 public String renameDocument(String documentId, String displayName)
320 throws FileNotFoundException {
321
322 StubDocument oldDoc = mStorage.get(documentId);
323
324 File before = oldDoc.file;
325 File after = new File(before.getParentFile(), displayName);
326
327 if (after.exists()) {
328 throw new IllegalStateException("Already exists " + after);
329 }
330
331 boolean result = before.renameTo(after);
332
333 if (!result) {
334 throw new IllegalStateException("Failed to rename to " + after);
335 }
336
337 StubDocument newDoc = StubDocument.createRegularDocument(after, oldDoc.mimeType,
338 mStorage.get(oldDoc.parentId));
339
340 mStorage.remove(documentId);
341 notifyParentChanged(oldDoc.parentId);
342 getContext().getContentResolver().notifyChange(
343 DocumentsContract.buildDocumentUri(mAuthority, oldDoc.documentId), null, false);
344
345 mStorage.put(newDoc.documentId, newDoc);
346 notifyParentChanged(newDoc.parentId);
347 getContext().getContentResolver().notifyChange(
348 DocumentsContract.buildDocumentUri(mAuthority, newDoc.documentId), null, false);
349
350 if (!TextUtils.equals(documentId, newDoc.documentId)) {
351 return newDoc.documentId;
352 } else {
353 return null;
354 }
355 }
356
357 @Override
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900358 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
359 throws FileNotFoundException {
Steve McKaybbeba522016-01-13 17:17:39 -0800360
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900361 final StubDocument document = mStorage.get(docId);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900362 if (document == null || !document.file.isFile()) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900363 throw new FileNotFoundException();
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900364 }
365 if ((document.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) {
366 throw new IllegalStateException("Tried to open a virtual file.");
367 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900368
369 if ("r".equals(mode)) {
Steve McKaybbeba522016-01-13 17:17:39 -0800370 if (mSimulateReadErrorIds.contains(docId)) {
371 Log.d(TAG, "Simulated errs enabled. Open in the wrong mode.");
372 return ParcelFileDescriptor.open(
373 document.file, ParcelFileDescriptor.MODE_WRITE_ONLY);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700374 }
Steve McKaybbeba522016-01-13 17:17:39 -0800375 return ParcelFileDescriptor.open(document.file, ParcelFileDescriptor.MODE_READ_ONLY);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900376 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900377 if ("w".equals(mode)) {
378 return startWrite(document);
379 }
Jun Ono418e5a32017-11-22 10:29:30 +0900380 if ("wa".equals(mode)) {
381 return startWrite(document, true);
382 }
383
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900384
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900385 throw new FileNotFoundException();
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900386 }
387
Ben Kwac06f3fd2015-04-24 15:35:25 -0700388 @VisibleForTesting
Ben Kwacb4461f2015-05-05 11:50:11 -0700389 public void simulateReadErrorsForFile(Uri uri) {
Steve McKaybbeba522016-01-13 17:17:39 -0800390 simulateReadErrorsForFile(DocumentsContract.getDocumentId(uri));
391 }
392
393 public void simulateReadErrorsForFile(String id) {
394 mSimulateReadErrorIds.add(id);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700395 }
396
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900397 @Override
398 public AssetFileDescriptor openDocumentThumbnail(
399 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
400 throw new FileNotFoundException();
401 }
402
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900403 @Override
404 public AssetFileDescriptor openTypedDocument(
Steve McKaybbeba522016-01-13 17:17:39 -0800405 String docId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900406 throws FileNotFoundException {
Steve McKaybbeba522016-01-13 17:17:39 -0800407 final StubDocument document = mStorage.get(docId);
Tomasz Mikolajewskie475d3b2015-12-24 13:24:00 +0900408 if (document == null || !document.file.isFile() || document.streamTypes == null) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900409 throw new FileNotFoundException();
410 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900411 for (final String mimeType : document.streamTypes) {
412 // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900413 // doesn't use them for getStreamTypes nor openTypedDocument.
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900414 if (mimeType.equals(mimeTypeFilter)) {
415 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
416 document.file, ParcelFileDescriptor.MODE_READ_ONLY);
Steve McKaybbeba522016-01-13 17:17:39 -0800417 if (mSimulateReadErrorIds.contains(docId)) {
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900418 pfd = new ParcelFileDescriptor(pfd) {
419 @Override
420 public void checkError() throws IOException {
421 throw new IOException("Test error");
422 }
423 };
424 }
425 return new AssetFileDescriptor(pfd, 0, document.file.length());
426 }
427 }
428 throw new IllegalArgumentException("Invalid MIME type filter for openTypedDocument().");
429 }
430
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900431 @Override
432 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
433 final StubDocument document = mStorage.get(DocumentsContract.getDocumentId(uri));
434 if (document == null) {
435 throw new IllegalArgumentException(
436 "The provided Uri is incorrect, or the file is gone.");
437 }
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900438 if (!"*/*".equals(mimeTypeFilter)) {
439 // Not used by DocumentsUI, so don't bother implementing it.
440 throw new UnsupportedOperationException();
441 }
Tomasz Mikolajewskie475d3b2015-12-24 13:24:00 +0900442 if (document.streamTypes == null) {
443 return null;
444 }
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900445 return document.streamTypes.toArray(new String[document.streamTypes.size()]);
446 }
447
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900448 private ParcelFileDescriptor startWrite(final StubDocument document)
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900449 throws FileNotFoundException {
Jun Ono418e5a32017-11-22 10:29:30 +0900450 return startWrite(document, false);
451 }
452
453 private ParcelFileDescriptor startWrite(final StubDocument document, boolean append)
454 throws FileNotFoundException {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900455 ParcelFileDescriptor[] pipe;
456 try {
457 pipe = ParcelFileDescriptor.createReliablePipe();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700458 } catch (IOException exception) {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900459 throw new FileNotFoundException();
460 }
461 final ParcelFileDescriptor readPipe = pipe[0];
462 final ParcelFileDescriptor writePipe = pipe[1];
463
Garfield Tan1e80be72017-02-27 18:16:00 -0800464 postToMainThread(() -> {
465 InputStream inputStream = null;
466 OutputStream outputStream = null;
467 try {
468 Log.d(TAG, "Opening write stream on file " + document.documentId);
469 inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPipe);
Jun Ono418e5a32017-11-22 10:29:30 +0900470 outputStream = new FileOutputStream(document.file, append);
Garfield Tan1e80be72017-02-27 18:16:00 -0800471 byte[] buffer = new byte[32 * 1024];
472 int bytesToRead;
473 int bytesRead = 0;
474 while (bytesRead != -1) {
475 synchronized (mWriteLock) {
476 // This cast is safe because the max possible value is buffer.length.
477 bytesToRead = (int) Math.min(document.rootInfo.getRemainingCapacity(),
478 buffer.length);
479 if (bytesToRead == 0) {
480 closePipeWithErrorSilently(readPipe, "Not enough space.");
481 break;
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900482 }
Garfield Tan1e80be72017-02-27 18:16:00 -0800483 bytesRead = inputStream.read(buffer, 0, bytesToRead);
484 if (bytesRead == -1) {
485 break;
486 }
487 outputStream.write(buffer, 0, bytesRead);
488 document.rootInfo.size += bytesRead;
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900489 }
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900490 }
Garfield Tan1e80be72017-02-27 18:16:00 -0800491 } catch (IOException e) {
492 Log.e(TAG, "Error on close", e);
493 closePipeWithErrorSilently(readPipe, e.getMessage());
494 } finally {
Jeff Sharkey94785ef2018-07-09 16:37:41 -0600495 FileUtils.closeQuietly(inputStream);
496 FileUtils.closeQuietly(outputStream);
Garfield Tan1e80be72017-02-27 18:16:00 -0800497 Log.d(TAG, "Closing write stream on file " + document.documentId);
498 notifyParentChanged(document.parentId);
499 getContext().getContentResolver().notifyChange(
500 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
501 null, false);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900502 }
Garfield Tan1e80be72017-02-27 18:16:00 -0800503 });
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900504
505 return writePipe;
506 }
507
508 private void closePipeWithErrorSilently(ParcelFileDescriptor pipe, String error) {
509 try {
510 pipe.closeWithError(error);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700511 } catch (IOException ignore) {
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900512 }
513 }
514
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700515 @Override
516 public Bundle call(String method, String arg, Bundle extras) {
Steve McKaya495c482016-02-04 10:02:09 -0800517 // We're not supposed to override any of the default DocumentsProvider
518 // methods that are supported by "call", so javadoc asks that we
519 // always call super.call first and return if response is not null.
520 Bundle result = super.call(method, arg, extras);
521 if (result != null) {
522 return result;
523 }
524
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700525 switch (method) {
526 case "clear":
527 clearCacheAndBuildRoots();
528 return null;
529 case "configure":
530 configure(arg, extras);
531 return null;
Steve McKaybbeba522016-01-13 17:17:39 -0800532 case "createVirtualFile":
533 return createVirtualFileFromBundle(extras);
534 case "simulateReadErrorsForFile":
535 simulateReadErrorsForFile(arg);
536 return null;
Aga Wronska18410b72016-01-26 14:06:29 -0800537 case "createDocumentWithFlags":
Steve McKaya495c482016-02-04 10:02:09 -0800538 return dispatchCreateDocumentWithFlags(extras);
Garfield Tan15666182017-01-18 16:27:55 -0800539 case "setLoadingDuration":
540 mLoadingDuration = extras.getLong(DocumentsContract.EXTRA_LOADING);
541 return null;
Garfield Tan1e80be72017-02-27 18:16:00 -0800542 case "waitForWrite":
543 waitForWrite();
544 return null;
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900545 }
Steve McKaya495c482016-02-04 10:02:09 -0800546
547 return null;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700548 }
549
Steve McKaybbeba522016-01-13 17:17:39 -0800550 private Bundle createVirtualFileFromBundle(Bundle extras) {
551 try {
552 Uri uri = createVirtualFile(
553 extras.getString(EXTRA_ROOT),
554 extras.getString(EXTRA_PATH),
555 extras.getString(Document.COLUMN_MIME_TYPE),
556 extras.getStringArrayList(EXTRA_STREAM_TYPES),
557 extras.getByteArray(EXTRA_CONTENT));
558
559 String documentId = DocumentsContract.getDocumentId(uri);
560 Bundle result = new Bundle();
561 result.putString(Document.COLUMN_DOCUMENT_ID, documentId);
562 return result;
563 } catch (IOException e) {
564 Log.e(TAG, "Couldn't create virtual file.");
565 }
566
567 return null;
568 }
569
Aga Wronska18410b72016-01-26 14:06:29 -0800570 private Bundle dispatchCreateDocumentWithFlags(Bundle extras) {
571 String rootId = extras.getString(EXTRA_PARENT_ID);
572 String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
573 String name = extras.getString(Document.COLUMN_DISPLAY_NAME);
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900574 List<String> streamTypes = extras.getStringArrayList(EXTRA_STREAM_TYPES);
Aga Wronska18410b72016-01-26 14:06:29 -0800575 int flags = extras.getInt(EXTRA_FLAGS);
576
577 Bundle out = new Bundle();
578 String documentId = null;
579 try {
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900580 documentId = createDocument(rootId, mimeType, name, flags, streamTypes);
Aga Wronska18410b72016-01-26 14:06:29 -0800581 Uri uri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
582 out.putParcelable(DocumentsContract.EXTRA_URI, uri);
583 } catch (FileNotFoundException e) {
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900584 Log.d(TAG, "Creating document with flags failed" + name);
Aga Wronska18410b72016-01-26 14:06:29 -0800585 }
586 return out;
587 }
588
Garfield Tan1e80be72017-02-27 18:16:00 -0800589 private void waitForWrite() {
590 try {
591 CountDownLatch latch = new CountDownLatch(1);
592 postToMainThread(latch::countDown);
593 latch.await();
594 Log.d(TAG, "All writing is done.");
595 } catch (InterruptedException e) {
596 // should never happen
597 throw new RuntimeException(e);
598 }
599 }
600
601 private void postToMainThread(Runnable r) {
602 new Handler(Looper.getMainLooper()).post(r);
603 }
604
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900605 public String createDocument(String parentId, String mimeType, String displayName, int flags,
606 List<String> streamTypes) throws FileNotFoundException {
Aga Wronska18410b72016-01-26 14:06:29 -0800607
608 StubDocument parent = mStorage.get(parentId);
609 File file = createFile(parent, mimeType, displayName);
610
611 final StubDocument document = StubDocument.createDocumentWithFlags(file, mimeType, parent,
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900612 flags, streamTypes);
Aga Wronska18410b72016-01-26 14:06:29 -0800613 mStorage.put(document.documentId, document);
614 Log.d(TAG, "Created document " + document.documentId);
615 notifyParentChanged(document.parentId);
616 getContext().getContentResolver().notifyChange(
617 DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
618 null, false);
619
620 return document.documentId;
621 }
622
623 private File createFile(StubDocument parent, String mimeType, String displayName)
624 throws FileNotFoundException {
625 if (parent == null) {
626 throw new IllegalArgumentException(
627 "Can't create file " + displayName + " in null parent.");
628 }
629 if (!parent.file.isDirectory()) {
630 throw new IllegalArgumentException(
631 "Can't create file " + displayName + " inside non-directory parent "
632 + parent.file.getName());
633 }
634
635 final File file = new File(parent.file, displayName);
636 if (file.exists()) {
637 throw new FileNotFoundException(
638 "Duplicate file names not supported for " + file);
639 }
640
641 if (mimeType.equals(Document.MIME_TYPE_DIR)) {
642 if (!file.mkdirs()) {
643 throw new FileNotFoundException("Failed to create directory(s): " + file);
644 }
645 Log.i(TAG, "Created new directory: " + file);
646 } else {
647 boolean created = false;
648 try {
649 created = file.createNewFile();
650 } catch (IOException e) {
651 // We'll throw an FNF exception later :)
652 Log.e(TAG, "createNewFile operation failed for file: " + file, e);
653 }
654 if (!created) {
655 throw new FileNotFoundException("createNewFile operation failed for: " + file);
656 }
657 Log.i(TAG, "Created new file: " + file);
658 }
659 return file;
660 }
661
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700662 private void configure(String arg, Bundle extras) {
663 Log.d(TAG, "Configure " + arg);
Steve McKay99bcc6a2015-10-26 17:03:55 -0700664 String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
Garfield Tanceedd1f2017-05-17 14:10:12 -0700665 long rootSize = extras.getLong(EXTRA_SIZE, 100) * 1024 * 1024;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700666 setSize(rootName, rootSize);
Garfield Tanceedd1f2017-05-17 14:10:12 -0700667 mRootNotification = extras.getBoolean(EXTRA_ENABLE_ROOT_NOTIFICATION, true);
Tomasz Mikolajewski80550442015-04-10 17:28:53 +0900668 }
669
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900670 private void notifyParentChanged(String parentId) {
671 getContext().getContentResolver().notifyChange(
672 DocumentsContract.buildChildDocumentsUri(mAuthority, parentId), null, false);
Garfield Tanceedd1f2017-05-17 14:10:12 -0700673 if (mRootNotification) {
674 // Notify also about possible change in remaining space on the root.
675 getContext().getContentResolver().notifyChange(
676 DocumentsContract.buildRootsUri(mAuthority), null, false);
677 }
Tomasz Mikolajewski38e965f2015-04-10 17:32:44 +0900678 }
679
Tomasz Mikolajewskibcda3d12015-04-13 13:27:14 +0900680 private void includeDocument(MatrixCursor result, StubDocument document) {
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900681 final RowBuilder row = result.newRow();
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900682 row.add(Document.COLUMN_DOCUMENT_ID, document.documentId);
683 row.add(Document.COLUMN_DISPLAY_NAME, document.file.getName());
684 row.add(Document.COLUMN_SIZE, document.file.length());
685 row.add(Document.COLUMN_MIME_TYPE, document.mimeType);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900686 row.add(Document.COLUMN_FLAGS, document.flags);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900687 row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified());
Tomasz Mikolajewski67f9bed2015-04-08 19:38:55 +0900688 }
689
Steve McKay99bcc6a2015-10-26 17:03:55 -0700690 private void removeChildrenRecursively(File file) {
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900691 for (File childFile : file.listFiles()) {
692 if (childFile.isDirectory()) {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700693 removeChildrenRecursively(childFile);
Tomasz Mikolajewski55194742015-04-08 09:21:08 +0900694 }
695 childFile.delete();
696 }
697 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900698
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700699 public void setSize(String rootId, long rootSize) {
700 RootInfo root = mRoots.get(rootId);
701 if (root != null) {
702 final String key = STORAGE_SIZE_KEY + "." + rootId;
703 Log.d(TAG, "Set size of " + key + " : " + rootSize);
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900704
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700705 // Persist the size.
706 SharedPreferences.Editor editor = mPrefs.edit();
707 editor.putLong(key, rootSize);
708 editor.apply();
709 // Apply the size in the current instance of this provider.
710 root.capacity = rootSize;
711 getContext().getContentResolver().notifyChange(
712 DocumentsContract.buildRootsUri(mAuthority),
713 null, false);
714 } else {
715 Log.e(TAG, "Attempt to configure non-existent root: " + rootId);
716 }
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900717 }
718
Ben Kwac06f3fd2015-04-24 15:35:25 -0700719 @VisibleForTesting
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900720 public Uri createRegularFile(String rootId, String path, String mimeType, byte[] content)
721 throws FileNotFoundException, IOException {
722 final File file = createFile(rootId, path, mimeType, content);
723 final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
724 if (parent == null) {
725 throw new FileNotFoundException("Parent not found.");
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700726 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900727 final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
728 mStorage.put(document.documentId, document);
Aga Wronska18410b72016-01-26 14:06:29 -0800729 return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
Ben Kwac06f3fd2015-04-24 15:35:25 -0700730 }
731
732 @VisibleForTesting
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900733 public Uri createVirtualFile(
734 String rootId, String path, String mimeType, List<String> streamTypes, byte[] content)
735 throws FileNotFoundException, IOException {
Steve McKaybbeba522016-01-13 17:17:39 -0800736
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900737 final File file = createFile(rootId, path, mimeType, content);
738 final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
739 if (parent == null) {
740 throw new FileNotFoundException("Parent not found.");
741 }
742 final StubDocument document = StubDocument.createVirtualDocument(
743 file, mimeType, streamTypes, parent);
744 mStorage.put(document.documentId, document);
Aga Wronska18410b72016-01-26 14:06:29 -0800745 return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
Tomasz Mikolajewskidcec9ac2015-12-16 16:23:00 +0900746 }
747
748 @VisibleForTesting
Ben Kwac06f3fd2015-04-24 15:35:25 -0700749 public File getFile(String rootId, String path) throws FileNotFoundException {
Steve McKay99bcc6a2015-10-26 17:03:55 -0700750 StubDocument root = mRoots.get(rootId).document;
Ben Kwac06f3fd2015-04-24 15:35:25 -0700751 if (root == null) {
752 throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
753 }
754 // Convert the path string into a path that's relative to the root.
755 File needle = new File(root.file, path.substring(1));
756
757 StubDocument found = mStorage.get(getDocumentIdForFile(needle));
758 if (found == null) {
759 return null;
760 }
761 return found.file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700762 }
763
Tomasz Mikolajewski049eb562015-12-18 11:29:48 +0900764 private File createFile(String rootId, String path, String mimeType, byte[] content)
765 throws FileNotFoundException, IOException {
Steve McKaybbeba522016-01-13 17:17:39 -0800766 Log.d(TAG, "Creating test file " + rootId + " : " + path);
Tomasz Mikolajewski049eb562015-12-18 11:29:48 +0900767 StubDocument root = mRoots.get(rootId).document;
768 if (root == null) {
769 throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
770 }
771 final File file = new File(root.file, path.substring(1));
772 if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
773 if (!file.mkdirs()) {
774 throw new FileNotFoundException("Couldn't create directory " + file.getPath());
775 }
776 } else {
777 if (!file.createNewFile()) {
778 throw new FileNotFoundException("Couldn't create file " + file.getPath());
779 }
780 try (final FileOutputStream fout = new FileOutputStream(file)) {
781 fout.write(content);
782 }
783 }
784 return file;
785 }
786
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900787 final static class RootInfo {
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800788 private static final int DEFAULT_ROOTS_FLAGS = Root.FLAG_SUPPORTS_SEARCH
789 | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD;
790
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700791 public final String name;
Steve McKay99bcc6a2015-10-26 17:03:55 -0700792 public final StubDocument document;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700793 public long capacity;
794 public long size;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800795 public int flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700796
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900797 RootInfo(File file, long capacity) {
798 this.name = file.getName();
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700799 this.capacity = 1024 * 1024;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800800 this.flags = DEFAULT_ROOTS_FLAGS;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700801 this.capacity = capacity;
802 this.size = 0;
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800803 this.document = StubDocument.createRootDocument(file, this);
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700804 }
805
806 public long getRemainingCapacity() {
807 return capacity - size;
808 }
Aga Wronskaee8ad9c2016-02-01 12:23:48 -0800809
810 public void setSearchEnabled(boolean enabled) {
811 flags = enabled ? (flags | Root.FLAG_SUPPORTS_SEARCH)
812 : (flags & ~Root.FLAG_SUPPORTS_SEARCH);
813 }
814
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700815 }
816
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900817 final static class StubDocument {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700818 public final File file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700819 public final String documentId;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900820 public final String mimeType;
821 public final List<String> streamTypes;
822 public final int flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700823 public final String parentId;
824 public final RootInfo rootInfo;
825
Aga Wronska18410b72016-01-26 14:06:29 -0800826 private StubDocument(File file, String mimeType, List<String> streamTypes, int flags,
827 StubDocument parent) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700828 this.file = file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700829 this.documentId = getDocumentIdForFile(file);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900830 this.mimeType = mimeType;
831 this.streamTypes = streamTypes;
832 this.flags = flags;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700833 this.parentId = parent.documentId;
834 this.rootInfo = parent.rootInfo;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700835 }
836
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900837 private StubDocument(File file, RootInfo rootInfo) {
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700838 this.file = file;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700839 this.documentId = getDocumentIdForFile(file);
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900840 this.mimeType = Document.MIME_TYPE_DIR;
Steve McKayb324dfd2016-10-28 12:52:36 -0700841 this.streamTypes = new ArrayList<>();
Aga Wronska3c237182016-01-20 16:32:33 -0800842 this.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_SUPPORTS_RENAME;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700843 this.parentId = null;
844 this.rootInfo = rootInfo;
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700845 }
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900846
847 public static StubDocument createRootDocument(File file, RootInfo rootInfo) {
848 return new StubDocument(file, rootInfo);
849 }
850
851 public static StubDocument createRegularDocument(
852 File file, String mimeType, StubDocument parent) {
Aga Wronska18410b72016-01-26 14:06:29 -0800853 int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900854 if (file.isDirectory()) {
855 flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
856 } else {
857 flags |= Document.FLAG_SUPPORTS_WRITE;
858 }
859 return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent);
860 }
861
Aga Wronska18410b72016-01-26 14:06:29 -0800862 public static StubDocument createDocumentWithFlags(
Tomasz Mikolajewskife7f5362016-03-02 17:41:40 +0900863 File file, String mimeType, StubDocument parent, int flags,
864 List<String> streamTypes) {
865 return new StubDocument(file, mimeType, streamTypes, flags, parent);
Aga Wronska18410b72016-01-26 14:06:29 -0800866 }
867
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900868 public static StubDocument createVirtualDocument(
869 File file, String mimeType, List<String> streamTypes, StubDocument parent) {
870 int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
871 | Document.FLAG_VIRTUAL_DOCUMENT;
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900872 return new StubDocument(file, mimeType, streamTypes, flags, parent);
873 }
874
Steve McKayf712a202015-11-19 17:27:12 -0800875 @Override
876 public String toString() {
877 return "StubDocument{"
878 + "path:" + file.getPath()
Steve McKayf712a202015-11-19 17:27:12 -0800879 + ", documentId:" + documentId
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900880 + ", mimeType:" + mimeType
881 + ", streamTypes:" + streamTypes.toString()
882 + ", flags:" + flags
Steve McKayf712a202015-11-19 17:27:12 -0800883 + ", parentId:" + parentId
Tomasz Mikolajewskicf08d6d2015-12-14 18:50:24 +0900884 + ", rootInfo:" + rootInfo
Steve McKayf712a202015-11-19 17:27:12 -0800885 + "}";
886 }
Ben Kwaaac9e2e2015-04-16 18:14:35 -0700887 }
888
889 private static String getDocumentIdForFile(File file) {
Tomasz Mikolajewski6e9d76d2015-04-13 12:17:51 +0900890 return file.getAbsolutePath();
891 }
892}