diff options
Diffstat (limited to 'packages/ExternalStorageProvider')
-rw-r--r-- | packages/ExternalStorageProvider/AndroidManifest.xml | 15 | ||||
-rw-r--r-- | packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java | 244 |
2 files changed, 258 insertions, 1 deletions
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 5272166..7094efc 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -13,7 +13,20 @@ android:permission="android.permission.MANAGE_DOCUMENTS"> <meta-data android:name="android.content.DOCUMENT_PROVIDER" - android:resource="@xml/document_provider" /> + android:value="true" /> + </provider> + + <!-- TODO: find a better place for tests to live --> + <provider + android:name=".TestDocumentsProvider" + android:authorities="com.example.documents" + android:grantUriPermissions="true" + android:exported="true" + android:permission="android.permission.MANAGE_DOCUMENTS" + android:enabled="false"> + <meta-data + android:name="android.content.DOCUMENT_PROVIDER" + android:value="true" /> </provider> </application> </manifest> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java new file mode 100644 index 0000000..872974f --- /dev/null +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2013 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.externalstorage; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; +import android.provider.DocumentsProvider; +import android.util.Log; + +import java.io.FileNotFoundException; +import java.lang.ref.WeakReference; + +public class TestDocumentsProvider extends DocumentsProvider { + private static final String TAG = "TestDocuments"; + + private static final boolean CRASH_ROOTS = false; + private static final boolean CRASH_DOCUMENT = false; + + private static final String MY_ROOT_ID = "myRoot"; + private static final String MY_DOC_ID = "myDoc"; + private static final String MY_DOC_NULL = "myNull"; + + private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, + Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, 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 Cursor queryRoots(String[] projection) throws FileNotFoundException { + if (CRASH_ROOTS) System.exit(12); + + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + final RowBuilder row = result.newRow(); + row.offer(Root.COLUMN_ROOT_ID, MY_ROOT_ID); + row.offer(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); + row.offer(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); + row.offer(Root.COLUMN_TITLE, "_Test title which is really long"); + row.offer(Root.COLUMN_SUMMARY, "_Summary which is also super long text"); + row.offer(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID); + row.offer(Root.COLUMN_AVAILABLE_BYTES, 1024); + return result; + } + + @Override + public Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException { + if (CRASH_DOCUMENT) System.exit(12); + + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, documentId); + return result; + } + + /** + * Holds any outstanding or finished "network" fetching. + */ + private WeakReference<CloudTask> mTask; + + private static class CloudTask implements Runnable { + + private final ContentResolver mResolver; + private final Uri mNotifyUri; + + private volatile boolean mFinished; + + public CloudTask(ContentResolver resolver, Uri notifyUri) { + mResolver = resolver; + mNotifyUri = notifyUri; + } + + @Override + public void run() { + // Pretend to do some network + Log.d(TAG, hashCode() + ": pretending to do some network!"); + SystemClock.sleep(2000); + Log.d(TAG, hashCode() + ": network done!"); + + mFinished = true; + + // Tell anyone remotely they should requery + mResolver.notifyChange(mNotifyUri, null, false); + } + + public boolean includeIfFinished(MatrixCursor result) { + Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished); + if (mFinished) { + includeFile(result, "_networkfile1"); + includeFile(result, "_networkfile2"); + includeFile(result, "_networkfile3"); + return true; + } else { + return false; + } + } + } + + private static class CloudCursor extends MatrixCursor { + public Object keepAlive; + public final Bundle extras = new Bundle(); + + public CloudCursor(String[] columnNames) { + super(columnNames); + } + + @Override + public Bundle getExtras() { + return extras; + } + } + + @Override + public Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + + final ContentResolver resolver = getContext().getContentResolver(); + final Uri notifyUri = DocumentsContract.buildDocumentUri( + "com.example.documents", parentDocumentId); + + CloudCursor result = new CloudCursor(resolveDocumentProjection(projection)); + result.setNotificationUri(resolver, notifyUri); + + // Always include local results + includeFile(result, MY_DOC_NULL); + includeFile(result, "localfile1"); + includeFile(result, "localfile2"); + + synchronized (this) { + // Try picking up an existing network fetch + CloudTask task = mTask != null ? mTask.get() : null; + if (task == null) { + Log.d(TAG, "No network task found; starting!"); + task = new CloudTask(resolver, notifyUri); + mTask = new WeakReference<CloudTask>(task); + new Thread(task).start(); + + // Aggressively try freeing weak reference above + new Thread() { + @Override + public void run() { + while (mTask.get() != null) { + SystemClock.sleep(200); + System.gc(); + System.runFinalization(); + } + Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!"); + } + }.start(); + } + + // Blend in cloud results if ready + if (task.includeIfFinished(result)) { + result.extras.putString(DocumentsContract.EXTRA_INFO, + "Everything Went Better Than Expected and this message is quite " + + "long and verbose and maybe even too long"); + result.extras.putString(DocumentsContract.EXTRA_ERROR, + "But then again, maybe our server ran into an error, which means " + + "we're going to have a bad time"); + } else { + result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); + } + + // Tie the network fetch to the cursor GC lifetime + result.keepAlive = task; + + return result; + } + } + + @Override + public Cursor queryRecentDocuments(String rootId, String[] projection) + throws FileNotFoundException { + // Pretend to take a super long time to respond + SystemClock.sleep(3000); + + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, "It was /worth/ the_wait for?the file:with the&incredibly long name"); + return result; + } + + @Override + public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) + throws FileNotFoundException { + throw new FileNotFoundException(); + } + + @Override + public boolean onCreate() { + return true; + } + + private static void includeFile(MatrixCursor result, String docId) { + final RowBuilder row = result.newRow(); + row.offer(Document.COLUMN_DOCUMENT_ID, docId); + row.offer(Document.COLUMN_DISPLAY_NAME, docId); + row.offer(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); + + if (MY_DOC_ID.equals(docId)) { + row.offer(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); + } else if (MY_DOC_NULL.equals(docId)) { + // No MIME type + } else { + row.offer(Document.COLUMN_MIME_TYPE, "application/octet-stream"); + } + } +} |