• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.cts.documentprovider;
18 
19 import android.content.res.AssetFileDescriptor;
20 import android.database.Cursor;
21 import android.database.MatrixCursor;
22 import android.database.MatrixCursor.RowBuilder;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.os.CancellationSignal;
27 import android.os.ParcelFileDescriptor;
28 import android.provider.DocumentsContract;
29 import android.provider.DocumentsContract.Document;
30 import android.provider.DocumentsContract.Root;
31 import android.provider.DocumentsProvider;
32 import android.util.Log;
33 
34 import java.io.ByteArrayOutputStream;
35 import java.io.FileNotFoundException;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 public class MyDocumentsProvider extends DocumentsProvider {
45     private static final String TAG = "TestDocumentsProvider";
46 
47     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
48             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
49             Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
50     };
51 
52     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
53             Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
54             Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
55     };
56 
resolveRootProjection(String[] projection)57     private static String[] resolveRootProjection(String[] projection) {
58         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
59     }
60 
resolveDocumentProjection(String[] projection)61     private static String[] resolveDocumentProjection(String[] projection) {
62         return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
63     }
64 
65     @Override
onCreate()66     public boolean onCreate() {
67         resetRoots();
68         return true;
69     }
70 
71     @Override
queryRoots(String[] projection)72     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
73         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
74 
75         RowBuilder row = result.newRow();
76         row.add(Root.COLUMN_ROOT_ID, "local");
77         row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
78         row.add(Root.COLUMN_TITLE, "CtsLocal");
79         row.add(Root.COLUMN_SUMMARY, "CtsLocalSummary");
80         row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
81 
82         row = result.newRow();
83         row.add(Root.COLUMN_ROOT_ID, "create");
84         row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
85         row.add(Root.COLUMN_TITLE, "CtsCreate");
86         row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
87 
88         return result;
89     }
90 
91     private Map<String, Doc> mDocs = new HashMap<>();
92 
93     private Doc mLocalRoot;
94     private Doc mCreateRoot;
95 
buildDoc(String docId, String displayName, String mimeType, String[] streamTypes)96     private Doc buildDoc(String docId, String displayName, String mimeType,
97             String[] streamTypes) {
98         final Doc doc = new Doc();
99         doc.docId = docId;
100         doc.displayName = displayName;
101         doc.mimeType = mimeType;
102         doc.streamTypes = streamTypes;
103         mDocs.put(doc.docId, doc);
104         return doc;
105     }
106 
resetRoots()107     public void resetRoots() {
108         Log.d(TAG, "resetRoots()");
109 
110         mDocs.clear();
111 
112         mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR, null);
113 
114         mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR, null);
115         mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
116 
117         {
118             Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1", null);
119             file1.contents = "fileone".getBytes();
120             file1.flags = Document.FLAG_SUPPORTS_WRITE;
121             mLocalRoot.children.add(file1);
122             mCreateRoot.children.add(file1);
123         }
124 
125         {
126             Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2", null);
127             file2.contents = "filetwo".getBytes();
128             file2.flags = Document.FLAG_SUPPORTS_WRITE;
129             mLocalRoot.children.add(file2);
130             mCreateRoot.children.add(file2);
131         }
132 
133         {
134             Doc virtualFile = buildDoc("doc:virtual-file", "VIRTUAL_FILE", "application/icecream",
135                     new String[] { "text/plain" });
136             virtualFile.flags = Document.FLAG_VIRTUAL_DOCUMENT;
137             virtualFile.contents = "Converted contents.".getBytes();
138             mLocalRoot.children.add(virtualFile);
139             mCreateRoot.children.add(virtualFile);
140         }
141 
142         Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR, null);
143         mLocalRoot.children.add(dir1);
144 
145         {
146             Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3", null);
147             file3.contents = "filethree".getBytes();
148             file3.flags = Document.FLAG_SUPPORTS_WRITE;
149             dir1.children.add(file3);
150         }
151 
152         Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR, null);
153         mCreateRoot.children.add(dir2);
154 
155         {
156             Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4", null);
157             file4.contents = "filefour".getBytes();
158             file4.flags = Document.FLAG_SUPPORTS_WRITE |
159                     Document.FLAG_SUPPORTS_COPY |
160                     Document.FLAG_SUPPORTS_MOVE |
161                     Document.FLAG_SUPPORTS_REMOVE;
162             dir2.children.add(file4);
163 
164             Doc subDir2 = buildDoc("doc:sub_dir2", "SUB_DIR2", Document.MIME_TYPE_DIR, null);
165             dir2.children.add(subDir2);
166         }
167     }
168 
169     private static class Doc {
170         public String docId;
171         public int flags;
172         public String displayName;
173         public long size;
174         public String mimeType;
175         public String[] streamTypes;
176         public long lastModified;
177         public byte[] contents;
178         public List<Doc> children = new ArrayList<>();
179 
include(MatrixCursor result)180         public void include(MatrixCursor result) {
181             final RowBuilder row = result.newRow();
182             row.add(Document.COLUMN_DOCUMENT_ID, docId);
183             row.add(Document.COLUMN_DISPLAY_NAME, displayName);
184             row.add(Document.COLUMN_SIZE, size);
185             row.add(Document.COLUMN_MIME_TYPE, mimeType);
186             row.add(Document.COLUMN_FLAGS, flags);
187             row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
188         }
189     }
190 
191     @Override
isChildDocument(String parentDocumentId, String documentId)192     public boolean isChildDocument(String parentDocumentId, String documentId) {
193         for (Doc doc : mDocs.get(parentDocumentId).children) {
194             if (doc.docId.equals(documentId)) {
195                 return true;
196             }
197             if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
198                 if (isChildDocument(doc.docId, documentId)) {
199                     return true;
200                 }
201             }
202         }
203         return false;
204     }
205 
206     @Override
createDocument(String parentDocumentId, String mimeType, String displayName)207     public String createDocument(String parentDocumentId, String mimeType, String displayName)
208             throws FileNotFoundException {
209         final String docId = "doc:" + System.currentTimeMillis();
210         final Doc doc = buildDoc(docId, displayName, mimeType, null);
211         doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
212         mDocs.get(parentDocumentId).children.add(doc);
213         return docId;
214     }
215 
216     @Override
renameDocument(String documentId, String displayName)217     public String renameDocument(String documentId, String displayName)
218             throws FileNotFoundException {
219         mDocs.get(documentId).displayName = displayName;
220         return null;
221     }
222 
223     @Override
deleteDocument(String documentId)224     public void deleteDocument(String documentId) throws FileNotFoundException {
225         final Doc doc = mDocs.get(documentId);
226         mDocs.remove(doc);
227         for (Doc parentDoc : mDocs.values()) {
228             parentDoc.children.remove(doc);
229         }
230     }
231 
232     @Override
removeDocument(String documentId, String parentDocumentId)233     public void removeDocument(String documentId, String parentDocumentId)
234             throws FileNotFoundException {
235         // There are no multi-parented documents in this provider, so it's safe to remove the
236         // document from mDocs.
237         final Doc doc = mDocs.get(documentId);
238         mDocs.remove(doc);
239         mDocs.get(parentDocumentId).children.remove(doc);
240     }
241 
242     @Override
copyDocument(String sourceDocumentId, String targetParentDocumentId)243     public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
244             throws FileNotFoundException {
245         final Doc doc = mDocs.get(sourceDocumentId);
246         if (doc.children.size() > 0) {
247             throw new UnsupportedOperationException("Recursive copy not supported for tests.");
248         }
249 
250         final Doc docCopy = buildDoc(doc.docId + "_copy", doc.displayName + "_COPY", doc.mimeType,
251                 doc.streamTypes);
252         mDocs.get(targetParentDocumentId).children.add(docCopy);
253         return docCopy.docId;
254     }
255 
256     @Override
moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)257     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
258             String targetParentDocumentId)
259             throws FileNotFoundException {
260         final Doc doc = mDocs.get(sourceDocumentId);
261         mDocs.get(sourceParentDocumentId).children.remove(doc);
262         mDocs.get(targetParentDocumentId).children.add(doc);
263         return doc.docId;
264     }
265 
266     @Override
queryDocument(String documentId, String[] projection)267     public Cursor queryDocument(String documentId, String[] projection)
268             throws FileNotFoundException {
269         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
270         mDocs.get(documentId).include(result);
271         return result;
272     }
273 
274     @Override
queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)275     public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
276             String sortOrder) throws FileNotFoundException {
277         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
278         for (Doc doc : mDocs.get(parentDocumentId).children) {
279             doc.include(result);
280         }
281         return result;
282     }
283 
284     @Override
openDocument(String documentId, String mode, CancellationSignal signal)285     public ParcelFileDescriptor openDocument(String documentId, String mode,
286             CancellationSignal signal) throws FileNotFoundException {
287         final Doc doc = mDocs.get(documentId);
288         if (doc == null) {
289             throw new FileNotFoundException();
290         }
291         if ((doc.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) {
292             throw new IllegalArgumentException("Tried to open a virtual file.");
293         }
294         return openDocumentUnchecked(doc, mode, signal);
295     }
296 
openDocumentUnchecked(final Doc doc, String mode, CancellationSignal signal)297     private ParcelFileDescriptor openDocumentUnchecked(final Doc doc, String mode,
298             CancellationSignal signal) throws FileNotFoundException {
299         final ParcelFileDescriptor[] pipe;
300         try {
301             pipe = ParcelFileDescriptor.createPipe();
302         } catch (IOException e) {
303             throw new IllegalStateException(e);
304         }
305         if (mode.contains("w")) {
306             new AsyncTask<Void, Void, Void>() {
307                 @Override
308                 protected Void doInBackground(Void... params) {
309                     synchronized (doc) {
310                         try {
311                             final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
312                                     pipe[0]);
313                             doc.contents = readFullyNoClose(is);
314                             is.close();
315                             doc.notifyAll();
316                         } catch (IOException e) {
317                             Log.w(TAG, "Failed to stream", e);
318                         }
319                     }
320                     return null;
321                 }
322             }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
323             return pipe[1];
324         } else {
325             new AsyncTask<Void, Void, Void>() {
326                 @Override
327                 protected Void doInBackground(Void... params) {
328                     synchronized (doc) {
329                         try {
330                             final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
331                                     pipe[1]);
332                             while (doc.contents == null) {
333                                 doc.wait();
334                             }
335                             os.write(doc.contents);
336                             os.close();
337                         } catch (IOException e) {
338                             Log.w(TAG, "Failed to stream", e);
339                         } catch (InterruptedException e) {
340                             Log.w(TAG, "Interuppted", e);
341                         }
342                     }
343                     return null;
344                 }
345             }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
346             return pipe[0];
347         }
348     }
349 
350     @Override
getStreamTypes(Uri documentUri, String mimeTypeFilter)351     public String[] getStreamTypes(Uri documentUri, String mimeTypeFilter) {
352         // TODO: Add enforceTree(uri); b/27156282
353         final String documentId = DocumentsContract.getDocumentId(documentUri);
354 
355         if (!"*/*".equals(mimeTypeFilter)) {
356             throw new UnsupportedOperationException(
357                     "Unsupported MIME type filter supported for tests.");
358         }
359 
360         final Doc doc = mDocs.get(documentId);
361         if (doc == null) {
362             return null;
363         }
364 
365         return doc.streamTypes;
366     }
367 
368     @Override
openTypedDocument( String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)369     public AssetFileDescriptor openTypedDocument(
370             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
371             throws FileNotFoundException {
372         final Doc doc = mDocs.get(documentId);
373         if (doc == null) {
374             throw new FileNotFoundException();
375         }
376 
377         if (mimeTypeFilter.contains("*")) {
378             throw new UnsupportedOperationException(
379                     "MIME type filters with Wildcards not supported for tests.");
380         }
381 
382         for (String streamType : doc.streamTypes) {
383             if (streamType.equals(mimeTypeFilter)) {
384                 return new AssetFileDescriptor(openDocumentUnchecked(
385                         doc, "r", signal), 0, doc.contents.length);
386             }
387         }
388 
389         throw new UnsupportedOperationException("Unsupported MIME type filter for tests.");
390     }
391 
readFullyNoClose(InputStream in)392     private static byte[] readFullyNoClose(InputStream in) throws IOException {
393         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
394         byte[] buffer = new byte[1024];
395         int count;
396         while ((count = in.read(buffer)) != -1) {
397             bytes.write(buffer, 0, count);
398         }
399         return bytes.toByteArray();
400     }
401 }
402