Home | History | Annotate | Download | only in documentprovider
      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 
     57     private static String[] resolveRootProjection(String[] projection) {
     58         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     59     }
     60 
     61     private static String[] resolveDocumentProjection(String[] projection) {
     62         return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
     63     }
     64 
     65     @Override
     66     public boolean onCreate() {
     67         resetRoots();
     68         return true;
     69     }
     70 
     71     @Override
     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 
     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 
    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 
    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
    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
    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
    217     public String renameDocument(String documentId, String displayName)
    218             throws FileNotFoundException {
    219         mDocs.get(documentId).displayName = displayName;
    220         return null;
    221     }
    222 
    223     @Override
    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
    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
    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
    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
    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
    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
    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 
    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
    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
    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 
    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