TestDocumentsProvider.java revision 3f4c205fd3110345241e690f2a2e7c1b477eac76
1/* 2 * Copyright (C) 2013 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.externalstorage; 18 19import android.content.ContentResolver; 20import android.database.Cursor; 21import android.database.MatrixCursor; 22import android.database.MatrixCursor.RowBuilder; 23import android.net.Uri; 24import android.os.Bundle; 25import android.os.CancellationSignal; 26import android.os.ParcelFileDescriptor; 27import android.os.SystemClock; 28import android.provider.DocumentsContract; 29import android.provider.DocumentsContract.Document; 30import android.provider.DocumentsContract.Root; 31import android.provider.DocumentsProvider; 32import android.util.Log; 33 34import java.io.FileNotFoundException; 35import java.lang.ref.WeakReference; 36 37public class TestDocumentsProvider extends DocumentsProvider { 38 private static final String TAG = "TestDocuments"; 39 40 private static final boolean CRASH_ROOTS = false; 41 private static final boolean CRASH_DOCUMENT = false; 42 43 private static final String MY_ROOT_ID = "myRoot"; 44 private static final String MY_DOC_ID = "myDoc"; 45 private static final String MY_DOC_NULL = "myNull"; 46 47 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 48 Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 49 Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, 50 Root.COLUMN_AVAILABLE_BYTES, 51 }; 52 53 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 54 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, 55 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 56 }; 57 58 private static String[] resolveRootProjection(String[] projection) { 59 return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 60 } 61 62 private static String[] resolveDocumentProjection(String[] projection) { 63 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 64 } 65 66 @Override 67 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 68 if (CRASH_ROOTS) System.exit(12); 69 70 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); 71 final RowBuilder row = result.newRow(); 72 row.offer(Root.COLUMN_ROOT_ID, MY_ROOT_ID); 73 row.offer(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); 74 row.offer(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); 75 row.offer(Root.COLUMN_TITLE, "_Test title which is really long"); 76 row.offer(Root.COLUMN_SUMMARY, "_Summary which is also super long text"); 77 row.offer(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID); 78 row.offer(Root.COLUMN_AVAILABLE_BYTES, 1024); 79 return result; 80 } 81 82 @Override 83 public Cursor queryDocument(String documentId, String[] projection) 84 throws FileNotFoundException { 85 if (CRASH_DOCUMENT) System.exit(12); 86 87 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 88 includeFile(result, documentId); 89 return result; 90 } 91 92 /** 93 * Holds any outstanding or finished "network" fetching. 94 */ 95 private WeakReference<CloudTask> mTask; 96 97 private static class CloudTask implements Runnable { 98 99 private final ContentResolver mResolver; 100 private final Uri mNotifyUri; 101 102 private volatile boolean mFinished; 103 104 public CloudTask(ContentResolver resolver, Uri notifyUri) { 105 mResolver = resolver; 106 mNotifyUri = notifyUri; 107 } 108 109 @Override 110 public void run() { 111 // Pretend to do some network 112 Log.d(TAG, hashCode() + ": pretending to do some network!"); 113 SystemClock.sleep(2000); 114 Log.d(TAG, hashCode() + ": network done!"); 115 116 mFinished = true; 117 118 // Tell anyone remotely they should requery 119 mResolver.notifyChange(mNotifyUri, null, false); 120 } 121 122 public boolean includeIfFinished(MatrixCursor result) { 123 Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished); 124 if (mFinished) { 125 includeFile(result, "_networkfile1"); 126 includeFile(result, "_networkfile2"); 127 includeFile(result, "_networkfile3"); 128 includeFile(result, "_networkfile4"); 129 includeFile(result, "_networkfile5"); 130 includeFile(result, "_networkfile6"); 131 return true; 132 } else { 133 return false; 134 } 135 } 136 } 137 138 private static class CloudCursor extends MatrixCursor { 139 public Object keepAlive; 140 public final Bundle extras = new Bundle(); 141 142 public CloudCursor(String[] columnNames) { 143 super(columnNames); 144 } 145 146 @Override 147 public Bundle getExtras() { 148 return extras; 149 } 150 } 151 152 @Override 153 public Cursor queryChildDocuments( 154 String parentDocumentId, String[] projection, String sortOrder) 155 throws FileNotFoundException { 156 157 final ContentResolver resolver = getContext().getContentResolver(); 158 final Uri notifyUri = DocumentsContract.buildDocumentUri( 159 "com.example.documents", parentDocumentId); 160 161 CloudCursor result = new CloudCursor(resolveDocumentProjection(projection)); 162 result.setNotificationUri(resolver, notifyUri); 163 164 // Always include local results 165 includeFile(result, MY_DOC_NULL); 166 includeFile(result, "localfile1"); 167 includeFile(result, "localfile2"); 168 includeFile(result, "localfile3"); 169 includeFile(result, "localfile4"); 170 171 synchronized (this) { 172 // Try picking up an existing network fetch 173 CloudTask task = mTask != null ? mTask.get() : null; 174 if (task == null) { 175 Log.d(TAG, "No network task found; starting!"); 176 task = new CloudTask(resolver, notifyUri); 177 mTask = new WeakReference<CloudTask>(task); 178 new Thread(task).start(); 179 180 // Aggressively try freeing weak reference above 181 new Thread() { 182 @Override 183 public void run() { 184 while (mTask.get() != null) { 185 SystemClock.sleep(200); 186 System.gc(); 187 System.runFinalization(); 188 } 189 Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!"); 190 } 191 }.start(); 192 } 193 194 // Blend in cloud results if ready 195 if (task.includeIfFinished(result)) { 196 result.extras.putString(DocumentsContract.EXTRA_INFO, 197 "Everything Went Better Than Expected and this message is quite " 198 + "long and verbose and maybe even too long"); 199 result.extras.putString(DocumentsContract.EXTRA_ERROR, 200 "But then again, maybe our server ran into an error, which means " 201 + "we're going to have a bad time"); 202 } else { 203 result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); 204 } 205 206 // Tie the network fetch to the cursor GC lifetime 207 result.keepAlive = task; 208 209 return result; 210 } 211 } 212 213 @Override 214 public Cursor queryRecentDocuments(String rootId, String[] projection) 215 throws FileNotFoundException { 216 // Pretend to take a super long time to respond 217 SystemClock.sleep(3000); 218 219 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 220 includeFile(result, "It was /worth/ the_wait for?the file:with the&incredibly long name"); 221 return result; 222 } 223 224 @Override 225 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) 226 throws FileNotFoundException { 227 throw new FileNotFoundException(); 228 } 229 230 @Override 231 public boolean onCreate() { 232 return true; 233 } 234 235 private static void includeFile(MatrixCursor result, String docId) { 236 final RowBuilder row = result.newRow(); 237 row.offer(Document.COLUMN_DOCUMENT_ID, docId); 238 row.offer(Document.COLUMN_DISPLAY_NAME, docId); 239 row.offer(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); 240 241 if (MY_DOC_ID.equals(docId)) { 242 row.offer(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 243 } else if (MY_DOC_NULL.equals(docId)) { 244 // No MIME type 245 } else { 246 row.offer(Document.COLUMN_MIME_TYPE, "application/octet-stream"); 247 } 248 } 249} 250