| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.documentprovider; |
| |
| import android.database.Cursor; |
| import android.database.MatrixCursor; |
| import android.database.MatrixCursor.RowBuilder; |
| import android.os.AsyncTask; |
| import android.os.CancellationSignal; |
| import android.os.ParcelFileDescriptor; |
| import android.provider.DocumentsContract.Document; |
| import android.provider.DocumentsContract.Root; |
| import android.provider.DocumentsProvider; |
| import android.util.Log; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class MyDocumentsProvider extends DocumentsProvider { |
| private static final String TAG = "TestDocumentsProvider"; |
| |
| private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { |
| Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, |
| Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, |
| }; |
| |
| private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { |
| Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, |
| Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, |
| }; |
| |
| private static String[] resolveRootProjection(String[] projection) { |
| return projection != null ? projection : DEFAULT_ROOT_PROJECTION; |
| } |
| |
| private static String[] resolveDocumentProjection(String[] projection) { |
| return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; |
| } |
| |
| @Override |
| public boolean onCreate() { |
| resetRoots(); |
| return true; |
| } |
| |
| @Override |
| public Cursor queryRoots(String[] projection) throws FileNotFoundException { |
| final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); |
| |
| RowBuilder row = result.newRow(); |
| row.add(Root.COLUMN_ROOT_ID, "local"); |
| row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); |
| row.add(Root.COLUMN_TITLE, "CtsLocal"); |
| row.add(Root.COLUMN_SUMMARY, "CtsLocalSummary"); |
| row.add(Root.COLUMN_DOCUMENT_ID, "doc:local"); |
| |
| row = result.newRow(); |
| row.add(Root.COLUMN_ROOT_ID, "create"); |
| row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD); |
| row.add(Root.COLUMN_TITLE, "CtsCreate"); |
| row.add(Root.COLUMN_DOCUMENT_ID, "doc:create"); |
| |
| return result; |
| } |
| |
| private Map<String, Doc> mDocs = new HashMap<>(); |
| |
| private Doc mLocalRoot; |
| private Doc mCreateRoot; |
| |
| private Doc buildDoc(String docId, String displayName, String mimeType) { |
| final Doc doc = new Doc(); |
| doc.docId = docId; |
| doc.displayName = displayName; |
| doc.mimeType = mimeType; |
| mDocs.put(doc.docId, doc); |
| return doc; |
| } |
| |
| public void resetRoots() { |
| Log.d(TAG, "resetRoots()"); |
| |
| mDocs.clear(); |
| |
| mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR); |
| |
| mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR); |
| mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE; |
| |
| { |
| Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1"); |
| file1.contents = "fileone".getBytes(); |
| file1.flags = Document.FLAG_SUPPORTS_WRITE; |
| mLocalRoot.children.add(file1); |
| mCreateRoot.children.add(file1); |
| } |
| |
| { |
| Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2"); |
| file2.contents = "filetwo".getBytes(); |
| file2.flags = Document.FLAG_SUPPORTS_WRITE; |
| mLocalRoot.children.add(file2); |
| mCreateRoot.children.add(file2); |
| } |
| |
| Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR); |
| mLocalRoot.children.add(dir1); |
| |
| { |
| Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3"); |
| file3.contents = "filethree".getBytes(); |
| file3.flags = Document.FLAG_SUPPORTS_WRITE; |
| dir1.children.add(file3); |
| } |
| |
| Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR); |
| mCreateRoot.children.add(dir2); |
| |
| { |
| Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4"); |
| file4.contents = "filefour".getBytes(); |
| file4.flags = Document.FLAG_SUPPORTS_WRITE; |
| dir2.children.add(file4); |
| } |
| } |
| |
| private static class Doc { |
| public String docId; |
| public int flags; |
| public String displayName; |
| public long size; |
| public String mimeType; |
| public long lastModified; |
| public byte[] contents; |
| public List<Doc> children = new ArrayList<>(); |
| |
| public void include(MatrixCursor result) { |
| final RowBuilder row = result.newRow(); |
| row.add(Document.COLUMN_DOCUMENT_ID, docId); |
| row.add(Document.COLUMN_DISPLAY_NAME, displayName); |
| row.add(Document.COLUMN_SIZE, size); |
| row.add(Document.COLUMN_MIME_TYPE, mimeType); |
| row.add(Document.COLUMN_FLAGS, flags); |
| row.add(Document.COLUMN_LAST_MODIFIED, lastModified); |
| } |
| } |
| |
| @Override |
| public boolean isChildDocument(String parentDocumentId, String documentId) { |
| for (Doc doc : mDocs.get(parentDocumentId).children) { |
| if (doc.docId.equals(documentId)) { |
| return true; |
| } |
| if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) { |
| return isChildDocument(doc.docId, documentId); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String createDocument(String parentDocumentId, String mimeType, String displayName) |
| throws FileNotFoundException { |
| final String docId = "doc:" + System.currentTimeMillis(); |
| final Doc doc = buildDoc(docId, displayName, mimeType); |
| doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME; |
| mDocs.get(parentDocumentId).children.add(doc); |
| return docId; |
| } |
| |
| @Override |
| public String renameDocument(String documentId, String displayName) |
| throws FileNotFoundException { |
| mDocs.get(documentId).displayName = displayName; |
| return null; |
| } |
| |
| @Override |
| public void deleteDocument(String documentId) throws FileNotFoundException { |
| mDocs.remove(documentId); |
| for (Doc doc : mDocs.values()) { |
| doc.children.remove(documentId); |
| } |
| } |
| |
| @Override |
| public Cursor queryDocument(String documentId, String[] projection) |
| throws FileNotFoundException { |
| final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); |
| mDocs.get(documentId).include(result); |
| return result; |
| } |
| |
| @Override |
| public Cursor queryChildDocuments(String parentDocumentId, String[] projection, |
| String sortOrder) throws FileNotFoundException { |
| final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); |
| for (Doc doc : mDocs.get(parentDocumentId).children) { |
| doc.include(result); |
| } |
| return result; |
| } |
| |
| @Override |
| public ParcelFileDescriptor openDocument(String documentId, String mode, |
| CancellationSignal signal) throws FileNotFoundException { |
| final Doc doc = mDocs.get(documentId); |
| if (doc == null) { |
| throw new FileNotFoundException(); |
| } |
| final ParcelFileDescriptor[] pipe; |
| try { |
| pipe = ParcelFileDescriptor.createPipe(); |
| } catch (IOException e) { |
| throw new IllegalStateException(e); |
| } |
| if (mode.contains("w")) { |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| try { |
| final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( |
| pipe[0]); |
| doc.contents = readFullyNoClose(is); |
| is.close(); |
| } catch (IOException e) { |
| Log.w(TAG, "Failed to stream", e); |
| } |
| return null; |
| } |
| }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| return pipe[1]; |
| } else { |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| try { |
| final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream( |
| pipe[1]); |
| os.write(doc.contents); |
| os.close(); |
| } catch (IOException e) { |
| Log.w(TAG, "Failed to stream", e); |
| } |
| return null; |
| } |
| }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| return pipe[0]; |
| } |
| } |
| |
| private static byte[] readFullyNoClose(InputStream in) throws IOException { |
| ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| byte[] buffer = new byte[1024]; |
| int count; |
| while ((count = in.read(buffer)) != -1) { |
| bytes.write(buffer, 0, count); |
| } |
| return bytes.toByteArray(); |
| } |
| } |